<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tombri Bowei</title>
    <description>The latest articles on Forem by Tombri Bowei (@_boweii).</description>
    <link>https://forem.com/_boweii</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1745361%2Fa5ccba4b-d4ad-4f12-8265-dcd18c3775cd.jpg</url>
      <title>Forem: Tombri Bowei</title>
      <link>https://forem.com/_boweii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/_boweii"/>
    <language>en</language>
    <item>
      <title>Trace AI: I Pointed a Camera at a Whiteboard. Notion Built the Entire System Design Doc.</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 29 Mar 2026 21:03:36 +0000</pubDate>
      <link>https://forem.com/_boweii/trace-ai-i-pointed-a-camera-at-a-whiteboard-notion-built-the-entire-system-design-doc-37m6</link>
      <guid>https://forem.com/_boweii/trace-ai-i-pointed-a-camera-at-a-whiteboard-notion-built-the-entire-system-design-doc-37m6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every engineering team has the same graveyard: folders full of blurry whiteboard photos that were supposed to become&lt;br&gt;&lt;br&gt;
  documentation. They never do. The meeting ends, the momentum dies, and that brilliant architecture sketch slowly rots in&lt;br&gt;&lt;br&gt;
  someone's camera roll.&lt;/p&gt;

&lt;p&gt;Trace AI kills that problem dead.&lt;/p&gt;

&lt;p&gt;Trace is an autonomous pipeline that watches your Notion Design Inbox, your Slack workspace, and your Discord server&lt;br&gt;&lt;br&gt;
  simultaneously. The moment you drop in a whiteboard photo, Trace wakes up, reasons through the sketch using Claude vision,&lt;br&gt;&lt;br&gt;
  and uses the official Notion MCP server to build a complete, structured system design document — entirely on its own.       &lt;/p&gt;

&lt;p&gt;Not a summary. Not a description. A full engineering document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mermaid.js architecture diagrams — flowchart and sequence, auto-generated from your ink&lt;/li&gt;
&lt;li&gt;Component breakdown — every service, database, and load balancer identified and described&lt;/li&gt;
&lt;li&gt;Security analysis — potential vulnerabilities flagged with recommendations&lt;/li&gt;
&lt;li&gt;Bottleneck detection — performance risks spotted before they hit production&lt;/li&gt;
&lt;li&gt;Actionable task cards — your handwritten To-Dos extracted and created as real entries in your Notion engineering board,
with Priority, Category, and Status pre-filled&lt;/li&gt;
&lt;li&gt;AWS cost estimates — rough monthly infrastructure projections for every component&lt;/li&gt;
&lt;li&gt;Complexity scoring — team size and build timeline estimates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire process takes under 2 minutes from photo to polished doc.&lt;/p&gt;

&lt;p&gt;In a large company, information decays. A project manager marks a project as "completed", but the task database still has 5 open items. A budget page says '$10k', but the invoice page says '$12k'. Usually, these contradictions stay hidden until something breaks.&lt;/p&gt;

&lt;p&gt;Sentinel fixes this. Using the new Model Context Protocol (MCP), Sentinel acts as a "shared brain" across your entire workspace. It observes every edit and cross-references it against a set of global truths you define. If a contradiction occurs, Sentinel doesn't just watch—it acts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/5n0E7xTc50c"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Boweii22/Arch-Vision" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Arch-Vision&lt;/a&gt;&lt;br&gt;
Deployed live on Render using Docker (nikolaik/python-nodejs for Python + Node.js in one container — required to run the MCP&lt;br&gt;
server subprocess).&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;This is where Trace AI goes beyond a simple API wrapper.&lt;/p&gt;

&lt;p&gt;Trace spawns the official @notionhq/notion-mcp-server as a live subprocess using the MCP Python SDK. Claude then enters an&lt;br&gt;&lt;br&gt;
  agentic tool-use loop — it doesn't receive a rigid set of instructions and execute them blindly. It reasons about what&lt;br&gt;&lt;br&gt;
  blocks to append, how to structure the content, and how to handle edge cases like long Mermaid diagrams that exceed Notion's&lt;br&gt;
   2000-character rich text limit.&lt;/p&gt;

&lt;p&gt;Whiteboard photo&lt;br&gt;
↓&lt;br&gt;
Claude Vision → Architecture Analysis (structured JSON)&lt;br&gt;
↓&lt;br&gt;
npx @notionhq/notion-mcp-server ←→ Claude agentic loop&lt;br&gt;
↓                                        ↓&lt;br&gt;
Notion Page created                 MCP tool calls:&lt;br&gt;
(direct API, correct parent)        - append_block_children&lt;br&gt;
                                    - create_database_page&lt;br&gt;
                                    - (up to 25 iterations)&lt;br&gt;
↓&lt;br&gt;
Tasks Database ← action items extracted&lt;br&gt;
Projects DB ← relational mapping&lt;/p&gt;

&lt;p&gt;MCP unlocks something that raw API calls can't: judgment. Claude decides when to split a code block, how to format a&lt;br&gt;&lt;br&gt;
  callout, when to skip a section because the data isn't there. The document isn't templated — it's reasoned.&lt;/p&gt;

&lt;p&gt;On top of that, Trace runs a bi-directional sync loop: if you manually edit the Mermaid diagram in Notion, Trace detects the   change, re-analyzes the modified architecture, and updates the analysis sections to stay consistent with your edits. Your&lt;br&gt;&lt;br&gt;
  Notion workspace stays alive, not static.&lt;/p&gt;

&lt;p&gt;Three ways to trigger it — Notion Inbox, Slack, Discord — because great tools meet teams where they already live.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>The 8-Month Feature Nobody Wanted (Including Me, Eventually)</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Thu, 26 Mar 2026 23:26:18 +0000</pubDate>
      <link>https://forem.com/_boweii/the-8-month-feature-nobody-wanted-including-me-eventually-4fg9</link>
      <guid>https://forem.com/_boweii/the-8-month-feature-nobody-wanted-including-me-eventually-4fg9</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built something so obsessively specific to my own workflow that I convinced myself it was universal. It wasn't. The moment I stopped refreshing analytics, it shipped better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem I Solved (For Myself)
&lt;/h2&gt;

&lt;p&gt;I was drowning in a particular flavor of repetitive work. Not a &lt;em&gt;common&lt;/em&gt; problem—my problem. The kind where you're the only one in your Slack thread who really gets why it's broken. So I decided: I'll build the fix.&lt;/p&gt;

&lt;p&gt;Eight months. Custom parsing logic, a UI that only I understood, database schema decisions that made sense only because I lived in that headspace daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Part
&lt;/h2&gt;

&lt;p&gt;I launched it to silence. Not criticism silence—&lt;em&gt;indifference&lt;/em&gt; silence. I refreshed GitHub stars. Checked analytics every 6 hours. Posted in communities. Nothing. The feature was &lt;em&gt;objectively good&lt;/em&gt;—it worked, it was fast, it solved the problem &lt;em&gt;it was designed for&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But it was designed for an audience of one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed Everything
&lt;/h2&gt;

&lt;p&gt;One afternoon I just... stopped checking. Deleted the analytics bookmark. Moved on to the next thing. &lt;/p&gt;

&lt;p&gt;That's when something weird happened: people started using it. Not because it went viral. But because the moment I stopped optimizing for adoption, I started shipping &lt;em&gt;faster&lt;/em&gt;. I added features because &lt;em&gt;I&lt;/em&gt; needed them. I fixed bugs I actually encountered. The product got honest.&lt;/p&gt;

&lt;p&gt;Turns out people can smell when you're building for them versus building for yourself. They prefer the latter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Specific beats universal.&lt;/strong&gt; A tool built with religious specificity for one person often beats a generic solution built for "everyone."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics are a trap.&lt;/strong&gt; The refresh-check-despair loop kills momentum. You don't need permission to build—you need focus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shipping &amp;gt; perfecting.&lt;/strong&gt; The moment I stopped trying to predict what others wanted, I built something worth using.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The real victory wasn't adoption. It was finishing something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're sitting on a feature you're "shocked nobody wants"—that's not failure feedback. That's a signal you're too deep in your own head. Either go deeper (own your specificity) or ship it and forget about it. The middle ground—optimizing for invisible users—is where projects die.&lt;/p&gt;

&lt;p&gt;What's the feature you've been gatekeeping because you think nobody else cares?&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>startup</category>
    </item>
    <item>
      <title>Remote Work Didn't Fix Toxic Culture—It Just Made It Async</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:32:57 +0000</pubDate>
      <link>https://forem.com/_boweii/remote-work-didnt-fix-toxic-culture-it-just-made-it-async-5hgj</link>
      <guid>https://forem.com/_boweii/remote-work-didnt-fix-toxic-culture-it-just-made-it-async-5hgj</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Your team's dysfunction didn't disappear when everyone went remote. It just moved into Slack, and now it's worse because you can't read the room.&lt;/p&gt;

&lt;p&gt;I spent 3 years watching this play out. Pre-pandemic, your manager's bad mood was obvious—you'd catch it in a hallway conversation. Now? That same manager writes passive-aggressive comments in PRs at 11 PM and you're supposed to interpret tone through a screen.&lt;/p&gt;

&lt;p&gt;The real problem: &lt;strong&gt;We mistook silence for health.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remote work did expose one thing clearly—bad code, bad decisions, bad people all leave a digital paper trail. But instead of fixing the culture, teams just learned to code-switch better. The senior engineer who bullied juniors in meetings? Now they bully them in GitHub comments. The meeting-obsessed manager? Just scheduled 8 async updates instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;p&gt;Old culture toxicity:&lt;br&gt;
  visibility = high&lt;br&gt;
  confrontation = forced&lt;br&gt;
  resolution = sometimes happens&lt;/p&gt;

&lt;p&gt;Remote culture toxicity:&lt;br&gt;
  visibility = hidden in timestamps&lt;br&gt;
  confrontation = deniable&lt;br&gt;
  resolution = "that's just how they communicate"&lt;/p&gt;

&lt;p&gt;The dysfunction didn't change. The accountability did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Culture is a system, not a setting. Flipping "remote" doesn't rewrite how people treat each other—it just changes the medium. I've seen amazing remote teams and soul-crushing ones. The difference? Teams that actually addressed &lt;em&gt;why&lt;/em&gt; people were unhappy, not just &lt;em&gt;where&lt;/em&gt; they worked.&lt;/p&gt;

&lt;p&gt;The teams that thrived remotely did something radical: they explicitly named their values, called out bad behavior in public channels, and held leadership accountable the same way they held junior devs accountable in code review.&lt;/p&gt;

&lt;p&gt;It took work. It wasn't comfortable. But it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Curious About
&lt;/h2&gt;

&lt;p&gt;Have you seen this in your org? Did remote work unmask problems or just rearrange them? And more importantly—did your team actually address the culture, or just accept a quieter version of the same dysfunction?&lt;/p&gt;

&lt;p&gt;Drop a comment. I'm betting you've got a story.&lt;/p&gt;

</description>
      <category>remotework</category>
      <category>devculture</category>
      <category>techleadership</category>
      <category>culturematters</category>
    </item>
    <item>
      <title>My AI Caught a £3,200 Scope Creep at 3am While I Was Asleep—Here's the Notion MCP System I Built</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Tue, 24 Mar 2026 17:27:37 +0000</pubDate>
      <link>https://forem.com/_boweii/my-ai-caught-a-ps3200-scope-creep-at-3am-while-i-was-asleep-heres-the-notion-mcp-system-i-built-1mnj</link>
      <guid>https://forem.com/_boweii/my-ai-caught-a-ps3200-scope-creep-at-3am-while-i-was-asleep-heres-the-notion-mcp-system-i-built-1mnj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/notion-2026-03-04"&gt;Notion MCP Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to show you something that happened at 3:17 am on a Tuesday.&lt;/p&gt;

&lt;p&gt;I was asleep. My phone was on silent. I wasn't thinking about work.&lt;br&gt;
But my Notion workspace was changing.&lt;/p&gt;

&lt;p&gt;A database row — a client called Holloway Studio — had its health score property quietly drop from 84 to 31. Its status flipped from "On track" to "Breach flagged". A new page appeared in its linked interaction log database: "Scope creep detected — client requested 4 additional deliverables beyond contracted scope in email thread at 23:42."&lt;/p&gt;

&lt;p&gt;And in a drafts section of the same Notion page, a complete, professional email had appeared — written by Claude AI, addressed to the client, asserting the contract terms, proposing a change order. Ready to send. Waiting for me when I woke up.&lt;/p&gt;

&lt;p&gt;I hadn't written a single line of that. I was asleep.&lt;/p&gt;

&lt;p&gt;That's what Notion MCP unlocked for me. Not a chatbot you talk to. It's not a dashboard you check. A system that runs — reads your world, reasons about it, and writes the results back to the place you actually work — while you do literally anything else.&lt;/p&gt;

&lt;p&gt;I built Contract OS. And I want to show you exactly how it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Contract OS is an autonomous AI agent that protects freelancers from the three things that silently drain their income every year:&lt;/p&gt;

&lt;p&gt;Scope creep — clients casually requesting extra work in emails, knowing most freelancers won't push back.&lt;/p&gt;

&lt;p&gt;Late payments — invoices that go a week past due, then two weeks, while you're too busy working to chase them.&lt;/p&gt;

&lt;p&gt;Revision abuse — clients burning through revision rounds the contract doesn't allow because nobody's counting.&lt;/p&gt;

&lt;p&gt;The system connects to Gmail, Google Calendar, and—at the absolute centre of everything—Notion, via the Notion MCP server. Every 6 hours, automatically, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads every active contract from a Notion database&lt;/li&gt;
&lt;li&gt;Fetches recent emails and calendar events for each client&lt;/li&gt;
&lt;li&gt;Sends all of it to Claude to detect breaches and score contract health&lt;/li&gt;
&lt;li&gt;Writes every result back to Notion — health scores, statuses, breach alerts, drafted emails, interaction logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notion isn't where the results get saved. Notion is the brain. The agent reads from it, reasons against it, and writes back to it in a continuous loop. Every cycle makes the workspace smarter.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Landing Page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before the dashboard, there's a landing page that explains the problem, shows the product, and gives you clear next steps whether you want to try the live demo or clone the repo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50gg7bu3y0kxkf2yrnqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50gg7bu3y0kxkf2yrnqz.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl66vyr78jvlg7aqpzr43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl66vyr78jvlg7aqpzr43.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh9uui8ozris4ibppd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oh9uui8ozris4ibppd7.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/fRoAKqk_djk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Show us the code
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/Boweii22/Contract-OS" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Contract-OS&lt;/a&gt;, Live Demo: &lt;a href="https://contract-os-dashboard.vercel.app/" rel="noopener noreferrer"&gt;https://contract-os-dashboard.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The live demo uses a seeded Notion workspace with fake client data — safe to explore fully. To run against your own contracts, clone and add your keys. The README walks through everything in about 15 minutes.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Boweii22/Contract-OS
&lt;span class="nb"&gt;cd &lt;/span&gt;contract-os
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env.local
&lt;span class="c"&gt;# Fill in: ANTHROPIC_API_KEY, NOTION_TOKEN, &lt;/span&gt;
&lt;span class="c"&gt;# NOTION_CONTRACTS_DB_ID, GOOGLE_CLIENT_ID,&lt;/span&gt;
&lt;span class="c"&gt;# GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Notion MCP
&lt;/h2&gt;

&lt;p&gt;This is the part I want to spend real time on — because the Notion MCP integration here is doing something I haven't seen any other project attempt. It's not a one-way pipe. It's a living read/write loop that makes Notion the persistent memory of an AI agent.&lt;/p&gt;

&lt;p&gt;Let me walk through every layer.&lt;/p&gt;

&lt;p&gt;The Notion database schema — contracts as structured AI-readable data&lt;br&gt;
The first thing I built was the Notion database. Two of them, actually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database 1: Contracts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every active client contract lives here as a structured row. Not just a name and a number — every property the AI needs to reason about a potential breach:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What the AI uses it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client name&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Primary identifier — also used as the Gmail search term to find that client's emails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract value&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Detects payment shortfalls and calculates outstanding balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment terms&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;The AI reads this to judge whether a payment is late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deposit received&lt;/td&gt;
&lt;td&gt;Checkbox&lt;/td&gt;
&lt;td&gt;Confirms whether the upfront payment has landed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deposit amount&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Cross-referenced against contract value to calculate what's still owed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance due&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Outstanding amount — triggers payment breach detection if overdue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadline&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Calculates deadline proximity risk — penalties kick in inside 14 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Flipped by the AI each cycle: On track / Scope creep / Breach flagged / Payment late&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health score&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Written by the AI — 0 to 100, validated against the deterministic formula&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revision rounds&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;The contracted allowance — the ceiling the AI enforces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revisions used&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Written back by the AI — compared against rounds to detect revision abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notice period days&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;Used to flag if a termination risk is approaching without proper notice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract type&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Fixed price / Retainer / Time &amp;amp; materials — affects how payment breach logic runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last signal&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The AI's one-sentence summary of the most recent finding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last synced&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of the last completed agent cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client email domain&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;@acmestudio.com&lt;/code&gt; — how the agent filters Gmail to the right client thread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notion log page URL&lt;/td&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;Direct link to the client's interaction log page in Notion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log database ID&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The ID of the linked log database — used by the agent to write new signal entries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;p&gt;The AI reads this entire row at the start of every cycle. This is what it knows. This is what it checks against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database 2: Interaction Log (one per client, linked sub-page)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every signal detected — every email flagged, every breach logged, every draft created — becomes a structured entry in this database. Timestamp d. Categorised. Permanent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What the AI uses it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Auto-generated entry title — e.g. &lt;code&gt;PAYMENT_BREACH · Nova Agency&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract&lt;/td&gt;
&lt;td&gt;Relation&lt;/td&gt;
&lt;td&gt;Links back to the parent contract row in the Contracts database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;Signal category — &lt;code&gt;SCOPE_CREEP&lt;/code&gt; / &lt;code&gt;PAYMENT_BREACH&lt;/code&gt; / &lt;code&gt;REVISION_BREACH&lt;/code&gt; / &lt;code&gt;FEEDBACK_DELAY&lt;/code&gt; / &lt;code&gt;DEADLINE_RISK&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Severity&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;How serious the finding is — &lt;code&gt;BREACH&lt;/code&gt; / &lt;code&gt;WARNING&lt;/code&gt; / &lt;code&gt;INFO&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;Human-readable explanation of exactly what was detected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evidence&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The raw data that triggered the signal — quoted email content, calendar events, or contract values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract clause violated&lt;/td&gt;
&lt;td&gt;Rich text&lt;/td&gt;
&lt;td&gt;The specific contract term that was broken or put at risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recommended action&lt;/td&gt;
&lt;td&gt;Select&lt;/td&gt;
&lt;td&gt;What the agent did next — &lt;code&gt;Draft email&lt;/code&gt; / &lt;code&gt;Log only&lt;/code&gt; / &lt;code&gt;Book meeting&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detected at&lt;/td&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;Timestamp of when the signal was fired during the agent cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health score at detection&lt;/td&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;The contract's health score at the exact moment the signal was logged — creates a timeline of decline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the audit trail. Every decision the AI ever made, recorded in Notion forever.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Reading contracts via Notion MCP — the agent's starting point&lt;/strong&gt;&lt;br&gt;
Every agent cycle begins with a single Notion MCP call: query the Contracts database, filter for active contracts, return everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/notion.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@notionhq/client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveContracts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NOTION_CONTRACTS_DB_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;does_not_equal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;does_not_equal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Archived&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contractValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contract value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentTerms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Payment terms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deadline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;revisionRounds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Revision rounds&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;revisionsUsed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Revisions used&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gmailLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gmail thread label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;logDatabaseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Log database ID&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;notionPageId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the foundation of everything. The agent knows nothing about your contracts except what Notion tells it. Notion is the source of truth.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Claude signal detection — the AI reasoning layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the contract data in hand, the agent fetches the last 15 emails from that client's Gmail thread and the next 30 days of calendar events. Then it sends all of it to Claude in a single, structured prompt — and Claude returns a clean JSON breach report.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// prompts/signal-detector.ts&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`You are a contract compliance AI for a freelancer.
You receive contract terms, recent client emails, and calendar data.
Detect any of these breach types:
- SCOPE_CREEP: work requested outside the contract scope
- PAYMENT_BREACH: payment overdue per the agreed terms
- REVISION_BREACH: more revisions requested than contracted
- FEEDBACK_DELAY: client has not responded within their window
- DEADLINE_RISK: deadline within 14 days with unresolved blockers

Respond ONLY with valid JSON. No markdown. No preamble. No explanation.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`CONTRACT TERMS:
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentTerms&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Deadline: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Revision rounds allowed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revisionRounds&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
Revisions used: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revisionsUsed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

RECENT EMAILS (last 15):
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;emailThreads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`[&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;snippet&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

CALENDAR EVENTS (next 30 days):
&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;calendarEvents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;

Respond with this exact JSON structure:
{
  "signals": [{
    "type": "SCOPE_CREEP|PAYMENT_BREACH|REVISION_BREACH|FEEDBACK_DELAY|DEADLINE_RISK",
    "severity": "INFO|WARNING|BREACH",
    "description": "One sentence finding",
    "evidence": "Direct quote from the email",
    "recommended_action": "Draft email|Book meeting|Log only",
    "contract_clause_violated": "Which term was breached"
  }],
  "health_score": &amp;lt;0-100&amp;gt;,
  "health_reasoning": "Two sentence explanation"
}`&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health score Claude returns gets validated against a deterministic formula — breach severity deductions, deadline proximity penalties, revision overuse penalties. The final score written to Notion is always the lower of the two. Claude cannot hallucinate your contract health upward.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Writing results back to Notion MCP — the loop closes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the moment I find most satisfying about this system. After Claude analyses a contract, every single result flows back into Notion via MCP. The database row updates. The interaction log grows. If an email needs drafting, the draft lands in Notion too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tools/notion.ts — write results back after every agent cycle&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateContractHealth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;healthScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;latestSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Update the contract database row&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notionPageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Health score&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;healthScore&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last signal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latestSignal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Last synced&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;appendInteractionLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Signal&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a new log entry in the client's linked database&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logDatabaseId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signal level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action taken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recommendedAction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveDraftEmailToNotion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The drafted email lives in Notion — survives page refresh,&lt;/span&gt;
  &lt;span class="c1"&gt;// survives server restart, accessible from any device&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;database_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logDatabaseId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Draft: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Draft email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Detail&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signal level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Breach&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Action taken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="na"&gt;rich_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Claude drafted response — awaiting send&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt; 
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write-back is what completes the loop. Notion isn't a destination. It's a relay station. The agent reads from it, Claude reasons against it, and the results flow back into it — richer than before, ready for the next cycle to read.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The bidirectional loop — visualised&lt;/strong&gt;&lt;br&gt;
Here's the full cycle, end to end, every 6 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Notion Contracts DB
       │
       │  notion.databases.query() — reads all active contracts
       ▼
  Agent Orchestrator
       │
       ├──► Gmail MCP — fetches last 15 emails per client
       ├──► Calendar MCP — fetches next 30 days of events
       │
       ▼
  Claude claude-sonnet-4-6
  (contract terms + emails + calendar → JSON breach report)
       │
       ▼
  Agent Orchestrator
       │
       ├──► notion.pages.update() — health score, status, last signal
       ├──► notion.pages.create() — new interaction log entry
       └──► notion.pages.create() — draft email saved to Notion
       │
       ▼
Notion Contracts DB
(now richer — ready for the next cycle to read)
       │
       ▼
  SSE broadcast → Dashboard updates in real time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three MCP servers. One agent. One brain — Notion. Every 6 hours, automatically, whether I'm at my desk or asleep.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Real-time dashboard — SSE from agent to UI&lt;/strong&gt;&lt;br&gt;
The dashboard connects to the agent via Server-Sent Events. The moment a contract is processed, the health score updates in the UI without a refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dashboard — useSSE hook&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/stream&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contract:updated&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setContracts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;updated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;updated&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signal:detected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setSignalFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The health ring animates. The breach badge appears. The signal card slides in. All from Notion data, flowing through Claude, pushed to the browser in real time.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The FullStack&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;AI model&lt;/strong&gt; — Claude &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;&lt;br&gt;
Structured JSON output, multi-source reasoning, zero hallucinated health scores&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primary MCP&lt;/strong&gt; — Notion MCP&lt;br&gt;
Reads contracts, writes health scores, creates log entries — the persistent brain&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting MCP&lt;/strong&gt; — Gmail MCP + Google Calendar MCP&lt;br&gt;
Real email threads + real calendar data fed directly into Claude&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent runtime&lt;/strong&gt; — Node.js 20 + TypeScript&lt;br&gt;
Fully typed orchestration, fast async parallel contract processing&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduler&lt;/strong&gt; — &lt;code&gt;node-cron&lt;/code&gt;&lt;br&gt;
Runs every 6 hours automatically — no manual triggers, no babysitting&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API + real-time&lt;/strong&gt; — Fastify + Server-Sent Events&lt;br&gt;
Sub-second health score push to dashboard the moment a contract is processed&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State cache&lt;/strong&gt; — &lt;code&gt;lowdb&lt;/code&gt;&lt;br&gt;
Flat-file JSON deduplication — never re-fires a signal Claude already caught&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dashboard&lt;/strong&gt; — Next.js 14 App Router&lt;br&gt;
Server components + streaming for instant first paint&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Animations&lt;/strong&gt; — Framer Motion 11&lt;br&gt;
Spring-based health ring, staggered loads, sliding tab indicator&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Styling&lt;/strong&gt; — Tailwind CSS v4&lt;br&gt;
Utility-first with CSS custom properties for the full design system&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fonts&lt;/strong&gt; — DM Serif Display + DM Mono + Geist&lt;br&gt;
Display headlines, monospace data values, clean UI body text&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hosting&lt;/strong&gt; — Railway (agent) + Vercel (dashboard)&lt;br&gt;
Persistent Node server for cron + edge-optimised Next.js — both free tier&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Notion MCP Actually Unlocked&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I want to be specific about this because it's easy to say "Notion MCP is central" and leave it vague. Here's what specifically would not be possible without it:&lt;/p&gt;

&lt;p&gt;Without Notion MCP reading contracts: The agent has no idea what your contract terms are. It cannot judge what's a breach versus what's normal. It's blind.&lt;/p&gt;

&lt;p&gt;Without Notion MCP writing health scores: The results of every AI analysis evaporate the moment the agent process ends. There's no memory. The next cycle starts from zero.&lt;/p&gt;

&lt;p&gt;Without Notion MCP creating log entries: There's no audit trail. No proof of what the AI detected, when it detected it, or what it did about it. The system is a black box.&lt;/p&gt;

&lt;p&gt;Without Notion MCP saving draft emails: The drafted response dies in memory. It never reaches you. It never becomes an action you can take.&lt;br&gt;
Notion MCP is what transforms this from a script that runs once into a system that learns and accumulates — every cycle building on the last, every result persisted, every decision traceable.&lt;/p&gt;

&lt;p&gt;That's the unlock. That's what makes it a product instead of a demo.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What's Next&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Slack + WhatsApp push alerts — breach detected at 3am? Get a message, not just a Notion update&lt;/li&gt;
&lt;li&gt;Contract generator — describe a project in plain English, get a full contract drafted and saved to Notion&lt;/li&gt;
&lt;li&gt;Client portal — a read-only Notion share link showing your client their deliverable statuses in real time&lt;/li&gt;
&lt;li&gt;Stripe webhook — detect payment received automatically, close the payment breach loop without reading emails&lt;/li&gt;
&lt;li&gt;Multi-currency — GBP-first today, trivial to extend&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Try It&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Live demo — fake data, fully interactive: contract-os.vercel.app&lt;br&gt;
GitHub — MIT licensed, clone and add your keys: &lt;a href="https://github.com/Boweii22/Contract-OS" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Contract-OS&lt;/a&gt;&lt;br&gt;
Landing page: &lt;a href="https://contract-os-dashboard.vercel.app/" rel="noopener noreferrer"&gt;https://contract-os-dashboard.vercel.app/&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you're a freelancer — I want to hear from you in the comments. Tell me about the scope creep that cost you. The invoice that went 30 days past due. The client who asked for "just one more small change" six times in a row.&lt;/p&gt;

&lt;p&gt;My guess is every single person reading this has a story. And I built this because I got tired of those stories being the price of doing business.&lt;/p&gt;

&lt;p&gt;They don't have to be.&lt;/p&gt;




&lt;p&gt;Built for the DEV × Notion MCP Challenge, March 2026.&lt;br&gt;
Claude + Notion MCP + Gmail + Google Calendar + Next.js + Framer Motion&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>notionchallenge</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>I Mapped My Entire Freelance Client Onboarding Process. It took 6 tools and 73 minutes. Here's What I Found.</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Wed, 18 Mar 2026 12:00:46 +0000</pubDate>
      <link>https://forem.com/_boweii/i-mapped-my-entire-freelance-client-onboarding-process-it-took-6-tools-and-73-minutes-heres-what-326</link>
      <guid>https://forem.com/_boweii/i-mapped-my-entire-freelance-client-onboarding-process-it-took-6-tools-and-73-minutes-heres-what-326</guid>
      <description>&lt;p&gt;And why do I think most freelancers have quietly accepted broken workflows as normal?&lt;/p&gt;




&lt;p&gt;Last month I decided to do something slightly embarrassing: I timed myself.&lt;/p&gt;

&lt;p&gt;Not during the project. Before it. I wanted to know exactly how long it took me to go from "yes, I'm interested in working with you" to "okay, the deposit's paid and we can start."&lt;/p&gt;

&lt;p&gt;The answer was 73 minutes. Across 6 different tools. For a single client.&lt;/p&gt;

&lt;p&gt;I'm a developer who's been freelancing for a few years. I've shipped dozens of projects. I have a favourable reputation. Clients come back. And yet somewhere along the way I'd built this completely invisible tax into every single engagement—a manual, fragmented, cobbled-together workflow that I'd never once stopped to actually look at.&lt;/p&gt;

&lt;p&gt;When I finally looked at it, I couldn't unsee it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Audit: What My "Process" Actually Looked Like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the exact sequence I went through, step by step:&lt;/p&gt;

&lt;p&gt;Step 1: The proposal (~25 minutes)&lt;br&gt;
Open a previous Google Doc. Start a new one — I never have a good template, so I'm always rebuilding from scratch. Write the scope. Second-guess the scope. Rewrite the scope. Format it to look professional. Export to PDF. Realise the PDF looks weird. Reformat. Export again.&lt;/p&gt;

&lt;p&gt;Step 2: The contract (~15 minutes)&lt;br&gt;
Open HelloSign (now Dropbox Sign). Find a contract template I saved two years ago. Update the client name, dates, and project scope. Send it. Wait for the client to create an account before they can even open it.&lt;/p&gt;

&lt;p&gt;Step 3: The deposit (~10 minutes)&lt;br&gt;
Send a PayPal invoice. Wonder if the client has PayPal. They don't always. Explain bank transfer as an alternative. Wait for them to figure out international transfer fees. Occasionally chase it 3 days later when it hasn't arrived.&lt;/p&gt;

&lt;p&gt;Step 4: The confirmation (~8 minutes)&lt;br&gt;
Email back and forth to confirm receipt of deposit, confirm project start date, and confirm which version of the scope we're working from.&lt;/p&gt;

&lt;p&gt;Step 5: The actual invoice (later, but it counts)&lt;br&gt;
Weeks later, open Wave (my invoice tool). Recreate the project details from memory. Send. Sometimes I get asked, "What is this for again?" because it looks nothing like the proposal.&lt;/p&gt;

&lt;p&gt;Total: ~73 minutes of admin, 6 different tools, zero of which talk to each other.&lt;/p&gt;

&lt;p&gt;And here's the part that really got me: none of this is visible in my work. A client sees a beautiful final deliverable. They have no idea how chaotic the paper trail behind it was.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Real Cost Nobody Talks About&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The obvious cost is time. 73 minutes per client adds up fast — if you onboard 15 clients a year, that's 18 hours of pure admin before a single line of code or pixel is moved.&lt;/p&gt;

&lt;p&gt;But the less obvious cost is professionalism.&lt;/p&gt;

&lt;p&gt;There's a gap between the quality of most freelancers' work and the quality of their onboarding process. You might have an incredible portfolio, strong references, and 5-star reviews — and then your client receives a Google Doc, a separate DocuSign email, a PayPal request, and three emails trying to stitch it all together.&lt;/p&gt;

&lt;p&gt;It doesn't match. And I think clients feel it even if they can't articulate it.&lt;/p&gt;

&lt;p&gt;A designer friend of mine put it best: "My proposals look nothing like my actual design work. My work is clean and intentional. My proposals are... a PDF I made in 2021 that I keep patching."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;I Asked Around. Turns Out This Is Almost Universal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After doing my own audit, I started asking other freelancers how they handle this. Developers, designers, photographers, copywriters, videographers. The answers were remarkably consistent:&lt;/p&gt;

&lt;p&gt;The most common setup (probably 60% of people I asked):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Google Docs or Word → PDF email → HelloSign/DocuSign → PayPal or bank transfer → Wave/FreshBooks invoice&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The aspirational setup (people who'd invested in proper tools):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HoneyBook or Bonsai or Dubsado&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The honest feedback about those proper tools:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Too expensive." / "Way more than I need." / "Took me weeks to set up and I barely used half the features." / "Designed for agencies, not individual freelancers."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HoneyBook is $39–79/month. Bonsai is $25–52/month. Dubsado has a learning curve that rivals some SaaS products I've been paid to build. These are serious tools for serious agencies. They're not really built for someone who wants to send a proposal and get paid.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Specific Things That Are Broken&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After all these conversations, here's what I think are the actual broken parts — in order of how painful they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There's no standard proposal format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every freelancer I spoke to described starting proposals from scratch or from an old document they keep patching. There's no "proposal.template" you can pull up and trust. Every client feels like the first client in terms of the process.&lt;/p&gt;

&lt;p&gt;The result: enormous variation in quality and a massive time sink on something that should be repeatable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proposals are static dead documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You send a PDF. The client receives it. You have zero idea what happens next. Did they open it? Did they read it? Did they forward it to someone else? Did they misplace it? You're flying blind from the moment you hit send.&lt;/p&gt;

&lt;p&gt;The anxiety of not knowing is surprisingly real. I've sent proposals and then spent three days wondering if the email even arrived.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The signature and payment are separate, awkward steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Right now, for most freelancers, a client has to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a DocuSign/ HelloSign email and create an account to sign&lt;/li&gt;
&lt;li&gt;Then separately figure out PayPal or bank transfer to pay the deposit&lt;/li&gt;
&lt;li&gt;Then email you to confirm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is three friction points between "yes, I want to work with you" and "money is moving." Each one is a moment where a client can lose momentum, get distracted, or decide it's more trouble than it's worth.&lt;/p&gt;

&lt;p&gt;I genuinely believe some clients ghost after receiving proposals not because they're not interested but because the process of saying yes is annoying enough that they put it off until they forget.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The tools don't talk to each other&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your proposal lives in Google Docs. Your contract lives in HelloSign. Your payment lives in PayPal. Your invoice lives in Wave. When you need to reference "what did we agree to" six weeks into a project, you're digging through four different apps trying to reconstruct the truth.&lt;/p&gt;

&lt;p&gt;There's no single source of record. And when disputes happen (they do), this matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything has to be rebuilt for every client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scope of the proposal changes per client. That's expected and fine. But the structure of the proposal? The payment terms? The contract language? The invoice format? These should be templates. d. They rarely are.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What a Better Version Looks Like&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've been thinking about what the ideal workflow actually looks like for an independent freelancer (not an agency, not a team — one person with good work and a handful of clients at any given time).&lt;/p&gt;

&lt;p&gt;Here's what I want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I fill in a form — client name, project, scope, pricing, timeline. Two minutes.&lt;/li&gt;
&lt;li&gt;A link is generated — a clean, professional-looking page that the client can open. Not a PDF attachment. A live URL.&lt;/li&gt;
&lt;li&gt;The client reads, signs, and pays — all on the same page. No separate tools. No account creation. Type their name to sign, enter card, done.&lt;/li&gt;
&lt;li&gt;I get notified, and funds move — I know the moment they opened it, signed it, and paid.&lt;/li&gt;
&lt;li&gt;Everything lives in one place — one dashboard with all proposals, their status, and the ability to convert any signed proposal into a final invoice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No CRM. No time tracking. No project management. Just create a proposal, send a link, and get paid.&lt;/p&gt;

&lt;p&gt;The version I have in my head is something like $15–20/month. Not $60/month. Not a 2-hour setup process. Something I could have running and used within the same afternoon I signed up.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Broader Pattern: Freelancers Are Remarkably Bad at Selling to Themselves&lt;/strong&gt;&lt;br&gt;
Here's the thing that strikes me most about all of this.&lt;/p&gt;

&lt;p&gt;Freelancers — especially developers and designers — are often incredibly good at building products. We understand UX. We know what makes a clean, frictionless flow. We can spot a broken onboarding process in a client's product from a mile away.&lt;/p&gt;

&lt;p&gt;And then we go home and send proposals in Google Docs and chase deposits over PayPal.&lt;/p&gt;

&lt;p&gt;There's a weird blind spot where we've accepted our own workflow as "just how it is" while we'd never let a client ship a product with that many friction points.&lt;/p&gt;

&lt;p&gt;I think part of it is that nobody teaches you this stuff. You start freelancing, you send your first proposal however you can, and then you just keep doing it that way. The tools you'd need to fix it cost too much or require too much setup, so the path of least resistance is to keep doing the broken thing.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What I'm Actually Curious About&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've shared my setup (chaos) and what I think the ideal looks like (one link, everything in one place). But I'm genuinely curious what other people have found.&lt;/p&gt;

&lt;p&gt;Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How long does your client onboarding take — from first "yes, interested" to deposit paid and project started?&lt;/li&gt;
&lt;li&gt;What's the actual worst part — is it the proposal writing, the contract, the deposit, the back-and-forth?&lt;/li&gt;
&lt;li&gt;If you've tried a proper tool (HoneyBook, Bonsai, Dubsado, anything else) — did it stick? Why or why not?&lt;/li&gt;
&lt;li&gt;What would you pay for something that genuinely solved this? Or does it not bother you enough to pay anything?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop your answers in the comments. Genuinely collecting these — not for any agenda, just because I think this problem is more interesting than it looks on the surface.&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>The Loneliness of Being the Only Dev in the Room</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Wed, 18 Mar 2026 05:24:33 +0000</pubDate>
      <link>https://forem.com/_boweii/the-loneliness-of-being-the-only-dev-in-the-room-fe2</link>
      <guid>https://forem.com/_boweii/the-loneliness-of-being-the-only-dev-in-the-room-fe2</guid>
      <description>&lt;p&gt;Nobody writes about this. Maybe because the people who live it are too busy being the only ones who can fix the WiFi.&lt;/p&gt;

&lt;p&gt;There's a specific kind of loneliness that has no name yet.&lt;/p&gt;

&lt;p&gt;It's not the loneliness of working remotely. It's not the loneliness of being an introvert in an open-plan office. It's sharper than both of those and quieter.&lt;/p&gt;

&lt;p&gt;It's the loneliness of being the only developer in a company full of people who don't speak your language — and never will.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You didn't notice it at first&lt;/strong&gt;&lt;br&gt;
The job sounded exciting. A real company. Real problems. And you—the person who could actually build things—walking in like someone who finally has superpowers in a world that needs them.&lt;/p&gt;

&lt;p&gt;The first few months felt like that. You shipped things. People were impressed. Someone in a meeting called you "our tech guy/girl", and you smiled because it felt like belonging.&lt;/p&gt;

&lt;p&gt;Then, slowly, quietly, you started to notice.&lt;/p&gt;

&lt;p&gt;There was no one to review your pull requests. Not because people were lazy — but because nobody could. You merged your own code. You reviewed your own architecture decisions. You Googled your own doubts.&lt;/p&gt;

&lt;p&gt;You started having conversations with yourself dressed up as documentation.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The weight of being the only one who knows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what nobody tells you about being the sole developer:&lt;/p&gt;

&lt;p&gt;You never get to be junior again.&lt;/p&gt;

&lt;p&gt;In a team, you can say, "I'm not sure; let me check with someone." You can say, "I think we should do X, but I want a second opinion." " You can be wrong in a safe container where someone catches you.&lt;/p&gt;

&lt;p&gt;When you're alone, every decision is final until it catastrophically isn't. The codebase becomes a direct mirror of your blind spots — and you can't see your blind spots. That's what makes them blind spots.&lt;/p&gt;

&lt;p&gt;The worst part? You can't tell anyone at work. Because to them, you're the expert. The wizard. The one with the answers. Admitting uncertainty feels like the whole illusion shatters.&lt;/p&gt;

&lt;p&gt;So you carry it. You carry the uncertainty, the technical debt you made at 2am when something was on fire, the architectural decisions you're not proud of, and the library you chose because it was familiar, not because it was right.&lt;/p&gt;

&lt;p&gt;You carry it alone.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The meetings. Oh, the meetings.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You sit in a room where someone is explaining a problem using words that mean something different to you than to them.&lt;/p&gt;

&lt;p&gt;"Can't we just make it automatic?"&lt;br&gt;
"How long would it take to just connect the two systems?"&lt;/p&gt;

&lt;p&gt;"We had something like this at my last company; they built it in a weekend."&lt;/p&gt;

&lt;p&gt;And you smile. Because explaining why it's not that simple requires first explaining what a system even is, what an API is, and what "connecting" two things actually means at 3am when the data formats don't match and one of the services has been deprecated and the documentation is a lie.&lt;br&gt;
You smile. You say, "I'll look into it." " You go back to your desk and stare at the ceiling for ten minutes.&lt;/p&gt;

&lt;p&gt;This isn't frustration at your colleagues. They're smart. They're good at what they do. This is something else — the particular ache of expertise becoming isolation.&lt;/p&gt;

&lt;p&gt;The more you know, the more alone you are in knowing it.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;You become the IT department by accident&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point — and you won't remember exactly when — you became responsible for things nobody assigned you.&lt;/p&gt;

&lt;p&gt;The printer. The WiFi. "My laptop is slow." "Can you look at this spreadsheet? Something's wrong with the formula." "We need a new tool for X; can you find one?" "Can you just automate this thing we do every Tuesday?"&lt;/p&gt;

&lt;p&gt;You say yes. Because you're kind. Because it's easier. Because saying "that's not really my job" when you're the only technical person feels like abandonment.&lt;/p&gt;

&lt;p&gt;But something quietly breaks inside you each time. Not because the tasks are beneath you. But because each one is a reminder: you are not here as a peer. You are here as infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The thing that actually hurts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's not the workload. It's not even the loneliness in the textbook sense.&lt;/p&gt;

&lt;p&gt;It's the absence of intellectual companionship.&lt;/p&gt;

&lt;p&gt;There's a specific joy that exists only when someone else deeply understands what you're building. When you can say, "I used an event-driven architecture here because—", before you finish the sentence, they're already nodding, already pushing back, already making it better.&lt;br&gt;
That conversation. That specific aliveness of two minds on the same problem.&lt;/p&gt;

&lt;p&gt;You haven't had it in months. Maybe years.&lt;/p&gt;

&lt;p&gt;You've had good conversations. Friendly conversations. But not that conversation. The one where you leave feeling like your brain grew.&lt;/p&gt;

&lt;p&gt;So instead you go to Twitter/X. You go to dev.to. You go to Hacker News and read comment threads at 11pm not because you're avoiding sleep but because you're desperately, quietly, trying to feel less alone in the thing you care most about.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What it does to you over time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It makes you doubt yourself in strange ways.&lt;/p&gt;

&lt;p&gt;Not the obvious "Am I good enough?" kind of doubt — though that's there too. But a subtler erosion. You start to wonder if the way you think about problems is normal. You have no reference point. No colleague who builds things like you do.&lt;/p&gt;

&lt;p&gt;You over-engineer sometimes, chasing elegance that nobody will ever see. You under-engineer other times, cutting corners you're ashamed of because there's no one to hold the standard with you.&lt;/p&gt;

&lt;p&gt;You develop strange habits. Talking to rubber ducks. Writing READMEs with the exhaustive detail of someone who fears being misunderstood. &lt;/p&gt;

&lt;p&gt;Commenting code is not for the next developer but as a kind of message in a bottle, proof that you were here, that you thought carefully, and that you weren't just someone who kept the lights on.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;But here's the thing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are thousands of us.&lt;/p&gt;

&lt;p&gt;In insurance companies and law firms and small NGOs and family businesses and local councils and restaurants that needed an app. Everywhere that the world runs on software but doesn't quite run as software yet.&lt;/p&gt;

&lt;p&gt;We are the quiet layer. The ones who translated "we need a thing" into a thing, alone, and then went home without anyone to debrief with.&lt;/p&gt;

&lt;p&gt;And for some reason—maybe because dev culture is so focused on startups and big tech and 10x teams and Silicon Valley origin stories—nobody writes about us.&lt;/p&gt;

&lt;p&gt;Our loneliness doesn't get a conference talk. Our specific exhaustion doesn't get a Medium think piece. Our wins are invisible because there's no one internally who understands what winning even looked like.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;To the only dev in the room&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your instinct to Google things isn't weakness. It's what self-reliance looks like in practice.&lt;/p&gt;

&lt;p&gt;The code you wrote alone, that nobody reviewed, that's been held up in production for two years — that counts. That's real. That took courage even if nobody clapped.&lt;/p&gt;

&lt;p&gt;The meetings you sat through smiling, translating the untranslatable — that's emotional labour nobody put on your job description, and you've been doing it quietly without credit.&lt;/p&gt;

&lt;p&gt;And the longing you feel for a peer, for someone who gets it — that's not neediness. That's just being human and loving something deeply enough to want to share it.&lt;/p&gt;




&lt;p&gt;Are you (or were you) the only dev at your company? Tell me what it was actually like in the comments. I promise I'll read every single one.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
      <category>devjournal</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Something the internet doesn't say enough</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 08 Mar 2026 14:28:54 +0000</pubDate>
      <link>https://forem.com/_boweii/something-the-internet-doesnt-say-enough-530l</link>
      <guid>https://forem.com/_boweii/something-the-internet-doesnt-say-enough-530l</guid>
      <description>&lt;p&gt;Some of the most brilliant, generous, and genuinely inspiring people I've come across in this dev community are women.&lt;/p&gt;

&lt;p&gt;@anjelica_, Jess Lee. Sylwia Laskowska, Sandra. Warda. And honestly — too many to name.&lt;/p&gt;

&lt;p&gt;We've never met in person. But a comment here, a post there, a like at the right moment — it counts more than you think.&lt;/p&gt;

&lt;p&gt;And offline? My mum taught me resilience before I even knew the word. My sisters keep me grounded. My girlfriend makes the hard days lighter.&lt;/p&gt;

&lt;p&gt;Happy International Women's Day to every woman building, writing, mentoring, debugging, and showing up.&lt;/p&gt;

&lt;p&gt;You are not background characters in this story. You're the plot. 🌍💙&lt;/p&gt;

</description>
      <category>womenintech</category>
      <category>devto</category>
      <category>community</category>
      <category>wecoded</category>
    </item>
    <item>
      <title>I Built an AI That Writes Victorian Funeral Rites for Dead Websites — Using 5 Specialised Agents 🕯️</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Fri, 06 Mar 2026 15:56:03 +0000</pubDate>
      <link>https://forem.com/_boweii/i-built-an-ai-that-writes-victorian-funeral-rites-for-dead-websites-using-5-specialised-agents-4hhb</link>
      <guid>https://forem.com/_boweii/i-built-an-ai-that-writes-victorian-funeral-rites-for-dead-websites-using-5-specialised-agents-4hhb</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is my submission for &lt;a href="https://dev.to/deved/build-multi-agent-systems"&gt;the DEV Education Track: Build Multi-Agent Systems with ADK&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here lies Flash Player. It asked you to update it one final time, and you said no."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a real output from my project. And honestly? I felt something.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;We've all seen "one giant prompt" AI apps. You throw everything at a single model and hope it figures it out. But what if the task is genuinely complex — requiring research, historical analysis, literary writing, sharp comedy, AND poetic compression all at once?&lt;br&gt;
That's exactly the problem I set out to solve. And the solution? Give each job to a specialist.&lt;/p&gt;

&lt;p&gt;Eulogy for the Internet is a multi-agent AI system that takes any website, app, or digital phenomenon and generates a full Victorian-style funeral obituary — as if historians from the year 2124 are looking back on it.&lt;/p&gt;

&lt;p&gt;You type in "Vine". Five AI agents spring into action. Thirty seconds later, you have a beautifully typeset parchment eulogy complete with a tombstone inscription that will make you simultaneously laugh and feel genuinely sad about a 6-second video app.&lt;/p&gt;

&lt;p&gt;Try it yourself: &lt;a href="https://eulogy-frontend-rqlox4dhaq-uc.a.run.app" rel="noopener noreferrer"&gt;https://eulogy-frontend-rqlox4dhaq-uc.a.run.app&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Disclaimer: This project is satire and creative commentary. All eulogies are fictional AI-generated writing for entertainment purposes only. No companies were harmed in the making of this application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Cloud Run Embed
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__cloud-run"&gt;
  &lt;iframe height="600px" src="https://eulogy-frontend-rqlox4dhaq-uc.a.run.app"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  Your Agents
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Instead of one monolithic prompt trying to do everything, I built 5 specialised agents, each an expert at exactly one thing. They're orchestrated via a coordinator that passes outputs between them like a relay race.&lt;br&gt;
Here's the full pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff38mmgpahdttzab4awy1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff38mmgpahdttzab4awy1.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔍 Agent 1: The Researcher&lt;br&gt;
What it does: Given any URL or topic, it searches for real facts — when it was founded, what it was famous for, who used it, and how it declined.&lt;br&gt;
Why a dedicated agent: Research requires breadth. You need facts first before any creative writing begins. Mixing research and writing in one prompt produces vague, hallucinated nonsense.&lt;/p&gt;

&lt;p&gt;Sample output for "Vine":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Vine was a short-form video hosting service founded in June 2012... acquired by Twitter in October 2012 before launch... at its peak, hosted content from creators like King Bach and Lele Pons with hundreds of millions of followers... shut down in January 2017 after Twitter announced discontinuation..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;📜 Agent 2: The Historian&lt;br&gt;
What it does: Takes the raw research and rewrites it through the lens of a historian writing in 2124, with 100 years of hindsight.&lt;br&gt;
Why a dedicated agent: This requires a complete shift in voice and temporal perspective. Asking a single prompt to be both factual AND a future historian simultaneously produces an identity crisis. Giving it its context produces something genuinely literary.&lt;/p&gt;

&lt;p&gt;Sample output:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Vine occupies a peculiar chapter in the annals of early digital culture — a platform whose entire creative vocabulary was six seconds long, and yet somehow those six seconds were enough to birth an entirely new form of celebrity, comedy, and human expression..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;✒️ Agent 3: The Eulogy Writer&lt;br&gt;
What it does: takes the research and historical framing and composes a proper three-act eulogy — What It Was, What It Wrought Upon the World, and On Its Undignified End.&lt;/p&gt;

&lt;p&gt;Why a dedicated agent: Literary writing requires full focus. This agent's only job is to be eloquent. It writes with drop caps, rolling sentences, and devastating final lines. No distraction.&lt;/p&gt;

&lt;p&gt;🕯️ Agent 4: The Roast Agent&lt;br&gt;
What it does: Writes the dissenting mourner's speech — the person at the funeral who loved the deceased but refuses to pretend they were perfect.&lt;br&gt;
Why a dedicated agent (and my personal favourite): Runs in parallel with the Eulogy Writer. While the writer crafts gravitas, the Roast Agent sharpens its skills. Every eulogy needs an uncomfortable truth-teller. This agent delivers.&lt;/p&gt;

&lt;p&gt;Sample output for Vine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Let us not romanticise the deceased overmuch. Vine was a website where fame was measured in loops, where a teenager doing a funny voice could accumulate more cultural influence than a documentary filmmaker with thirty years of experience... I shall miss the creativity it accidentally unlocked. I shall not miss what we became while watching it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;🪦 Agent 5: The Epitaph Agent&lt;br&gt;
What it does: Reads everything — all four previous outputs — and distils the entire eulogy into a single tombstone inscription. Maximum 15 words. Must be something you'd screenshot.&lt;br&gt;
Why a dedicated agent: Compression is its own art form. You cannot ask the same model that just wrote 800 words of flowing prose to suddenly become a master of the pithy one-liner. The Epitaph Agent thinks in epitaphs and nothing else.&lt;/p&gt;

&lt;p&gt;Sample outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vine: "It gave the world six seconds of genius, then ran out of time."&lt;/li&gt;
&lt;li&gt;Flash Player: "It asked you to update one final time. You said no."&lt;/li&gt;
&lt;li&gt;Google+: "Built by engineers who understood computers but not loneliness."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🎭 The Orchestrator&lt;br&gt;
What it does: Coordinates the entire pipeline. Calls Researcher → Historian → (Writer + Roast in parallel) → Epitaph. Passes outputs between agents. The system then returns the final assembled JSON to the frontend.&lt;/p&gt;

&lt;p&gt;Why parallel execution: The Writer and Roast agents don't need each other's output — they both just need the research and history. Running them in parallel cuts response time nearly in half.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Specialisation genuinely produces better output&lt;br&gt;
I was sceptical at first. Surely one good prompt could do all this? I was wrong. When I tested a single-prompt version against the multi-agent version, the difference was stark. The single prompt hedged everything, wrote blandly, and produced generic output. Each specialised agent went deep because it had one job and full context for that one job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deployment is where ideas meet reality — hard&lt;br&gt;
I hit every possible deployment error: billing not enabled, wrong IAM permissions, container startup timeouts, model names that weren't available on my account, and environment variables with literal newline characters baked into them from shell output. Every single one was solvable, but each one required understanding why it broke. The debugging process taught me more about Cloud Run than any tutorial would have.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The A2 A pattern is powerful but the contract matters&lt;br&gt;
Agent-to-agent communication lives and dies by the contract between agents. I had to be very deliberate about what each agent returns — structured JSON vs plain text, how errors bubble up, and what happens when one agent in the chain produces unexpected output. Getting the Epitaph Agent to reliably return JSON (and not wrap it in markdown code fences half the time) was its own mini-battle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Parallelism is free speed&lt;br&gt;
Running the Eulogy Writer and Roast Agent in parallel using asyncio.gather() was one of the most satisfying moments of the build. Two agents, running simultaneously, each producing their best work, then both landing at the Epitaph Agent at roughly the same time. It felt like watching a well-drilled team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The creative constraint made it better&lt;br&gt;
Forcing the Epitaph Agent to produce a maximum 15-word tombstone inscription sounds limiting. It's actually what makes the whole project shareable. People don't screenshot 800-word eulogies. They screenshot one devastating sentence. The constraint is the feature.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add tone selection — Tragic / Comic / Academic / Shakespearean&lt;/li&gt;
&lt;li&gt;Generate a shareable image card of the epitaph for social media&lt;/li&gt;
&lt;li&gt;Add real web search to the Researcher agent for even more accurate facts&lt;/li&gt;
&lt;li&gt;A "Roast My Startup" mode where you paste your own product URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built with Google ADK, Gemini 1.5 Flash, Cloud Run, FastAPI, and A2A Protocol&lt;br&gt;
Five agents. One funeral. Zero survivors.&lt;br&gt;
🕯️&lt;/p&gt;

</description>
      <category>agents</category>
      <category>buildmultiagents</category>
      <category>gemini</category>
      <category>adk</category>
    </item>
    <item>
      <title>I built a tool that turns bugs into diary entries—and debugging it taught me more than the tool itself</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Tue, 03 Mar 2026 13:38:04 +0000</pubDate>
      <link>https://forem.com/_boweii/i-built-a-tool-that-turns-bugs-into-diary-entries-and-debugging-it-taught-me-more-than-the-tool-i0a</link>
      <guid>https://forem.com/_boweii/i-built-a-tool-that-turns-bugs-into-diary-entries-and-debugging-it-taught-me-more-than-the-tool-i0a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/mlh-built-with-google-gemini-02-25-26"&gt;Built with Google Gemini: Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built with Google Gemini
&lt;/h2&gt;

&lt;p&gt;Every developer has a ritual they're quietly ashamed of: Google the error, copy the fix, paste it in, move on. No reflection. No understanding. Same mistake next week.&lt;/p&gt;

&lt;p&gt;I built DebugDiary to break that cycle.&lt;/p&gt;

&lt;p&gt;DebugDiary is a full-stack web app where you paste your broken code, describe what went wrong in plain English, and Google Gemini doesn't just fix it — it writes you a personal diary entry. A reflection. A mirror.&lt;/p&gt;

&lt;p&gt;Each entry contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A poetic title ("The Async Blind Spot", "When JavaScript Doesn't Wait for You")&lt;/li&gt;
&lt;li&gt;The technical root cause, explained clearly&lt;/li&gt;
&lt;li&gt;A deeper psychological lesson — why this mistake happens, not just what it is&lt;/li&gt;
&lt;li&gt;A "Remember This" rule of thumb burned into an inky black card&lt;/li&gt;
&lt;li&gt;Tags that categorise your error type over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dashboard then builds up a personal map of your weaknesses — your most common bug categories, your streak of consecutive days logging bugs, and an animated breakdown of your error patterns. Over time, it stops being a debugger and starts being a journal of your growth as a developer.&lt;/p&gt;

&lt;p&gt;Gemini plays the central role: every entry is generated by a carefully engineered prompt that instructs it to respond not like Stack Overflow, but like a thoughtful senior developer who's seen your mistake a hundred times and wants you to actually learn from it this time. The response comes back as structured JSON — title, fix, root cause, deeper lesson, pattern warning, tags, severity, mood — which the app maps directly into a beautiful, readable diary page.&lt;/p&gt;

&lt;p&gt;Tech stack: React + Vite frontend, Node.js + Express backend, Google Gemini API (&lt;code&gt;gemini-2.5-flash&lt;/code&gt;), local JSON persistence, Framer Motion animations, React Syntax Highlighter for code diffs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Demo URL: &lt;a href="https://debug-diary-ten.vercel.app/" rel="noopener noreferrer"&gt;https://debug-diary-ten.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8hnlcmr37smxr6bz0y8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8hnlcmr37smxr6bz0y8.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6d3t6azmghgym7vj3vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6d3t6azmghgym7vj3vc.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4aax4sjnyir5oo0v8tk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4aax4sjnyir5oo0v8tk.png" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;I learned more building DebugDiary in one evening than I expected — and most of it wasn't what I planned to learn.&lt;/p&gt;

&lt;p&gt;Technically, I deepened my understanding of prompt engineering in ways that surprised me. My first attempt at the Gemini prompt was a wall of instructions that returned inconsistent, unparseable JSON. The breakthrough came when I stopped telling Gemini what to do and started showing it the exact output shape I needed, with no room for interpretation. Shorter, more constrained prompts produced dramatically better structured output than longer, more descriptive ones. That's a counterintuitive lesson I'll carry into every AI integration I build.&lt;/p&gt;

&lt;p&gt;I also ran headfirst into the moving target that is the Gemini model ecosystem. Within a single evening I hit 404s from deprecated model names (&lt;code&gt;gemini-1.5-flash&lt;/code&gt;, &lt;code&gt;gemini-2.0-flash&lt;/code&gt;, &lt;code&gt;gemini-2.0-flash-001&lt;/code&gt; — all returned "no longer available for new users"), SDK version mismatches routing to the wrong API endpoint, and 429 rate limits on the free tier. I ended up switching from the &lt;code&gt;@google/generative-ai&lt;/code&gt; SDK to direct fetch calls against the REST API, then back to the SDK once I identified the correct model string (&lt;code&gt;gemini-2.5-flash&lt;/code&gt;) and endpoint (&lt;code&gt;v1beta&lt;/code&gt;). It was frustrating in the moment. In retrospect, it was the most valuable part of the build — I now understand the Gemini API surface deeply rather than just cargo-culting a quickstart.&lt;/p&gt;

&lt;p&gt;On the soft skills side, I learned something about scope. My original plan was a full database, user authentication, sharing features, and a weekly email digest. What I shipped was a local JSON file and four clean pages. And it's better for it. The constraints forced clarity — every design decision became about the core loop: paste bug, get lesson, see pattern. Nothing else.&lt;/p&gt;

&lt;p&gt;The most unexpected lesson was noticing the irony of what I'd built. DebugDiary is a tool for learning from your mistakes. And building it, I made more mistakes per hour than I have in months. Every 404, every malformed JSON response, every SDK quirk — DebugDiary would have written a diary entry about each one. The tool ate its own dog food before I'd even finished building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Gemini Feedback
&lt;/h2&gt;

&lt;p&gt;What worked brilliantly: The quality of Gemini's reflective writing genuinely surprised me. I expected it to produce dry, technical explanations. Instead, when prompted correctly, it produced entries that felt warm and human — the kind of feedback you'd get from a patient mentor rather than a linter. The "deeper lesson" section in particular consistently hit a level of psychological insight I didn't anticipate. Lines like "under time pressure, you revert to synchronous thinking" aren't just accurate — they're the kind of observation that makes you pause and recognise yourself.&lt;/p&gt;

&lt;p&gt;The speed of &lt;code&gt;gemini-2.5-flash&lt;/code&gt; is also excellent for this use case. Diary entries generate in 3–5 seconds, which feels fast enough to not break the flow of a debugging session.&lt;/p&gt;

&lt;p&gt;Where I ran into friction: The model availability situation is genuinely rough for new developers. I spent a significant portion of my build time fighting 404 errors caused by model names that appear in tutorials and documentation but are no longer available for new API accounts. There's no clear "here's what you should actually use if you created an account this month" guide. For a developer experience that's supposed to feel magical, the onboarding friction is real and it's the first thing a new user hits.&lt;/p&gt;

&lt;p&gt;The structured JSON output reliability also has room to improve. Even with explicit instructions to return only raw JSON, Gemini occasionally wrapped the response in markdown code fences, which broke my parser until I added a cleanup step. A native structured output mode — where you pass a schema and get guaranteed valid JSON back — would eliminate this entirely and make Gemini dramatically more useful for production applications that depend on parseable responses.&lt;/p&gt;

&lt;p&gt;Despite the friction, I'd use Gemini again without hesitation. The output quality when it works is genuinely ahead of what I've seen from other models for this kind of reflective, nuanced text generation. I just want the path from "I have an API key" to "I have working output" to be shorter.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>geminireflections</category>
      <category>gemini</category>
    </item>
    <item>
      <title>BridgeTheGap: Turning GitHub Intimidation into First PRs</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sat, 28 Feb 2026 23:33:40 +0000</pubDate>
      <link>https://forem.com/_boweii/bridgethegap-turning-github-intimidation-into-first-prs-1c7d</link>
      <guid>https://forem.com/_boweii/bridgethegap-turning-github-intimidation-into-first-prs-1c7d</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;I built this for the aspiring open-source contributor.&lt;/p&gt;

&lt;p&gt;We’ve all been there: You want to contribute to a major project, but you open a repository and feel hit by a "Wall of Code". It’s intimidating, the issues are full of jargon, and you don’t know where to start. This community is full of talent, but they are often stuck behind a "contribution paralysis" barrier. I wanted to build a bridge to help them cross it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;FirstPRCpncierge is an AI-powered "Contribution Concierge".&lt;/p&gt;

&lt;p&gt;Instead of scrolling through thousands of GitHub issues, users simply enter their GitHub username. The app analyses their most-used languages and matches them with real-time "Good First Issues" that actually fit their skill level.&lt;/p&gt;

&lt;p&gt;The "Magic" Feature:&lt;br&gt;
The app uses AI to "demystify" complex issues. It takes a technical GitHub description and translates it into a 3-step beginner roadmap:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📂 Where to look: The specific folders to explore.&lt;/li&gt;
&lt;li&gt;🛠️ What to change: A plain-English explanation of the logic.&lt;/li&gt;
&lt;li&gt;🧪 How to test: How to verify the fix works.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;✨ Try FirstPRConcierge. Live: &lt;a href="https://first-pr-concierge.vercel.app/" rel="noopener noreferrer"&gt;https://first-pr-concierge.vercel.app/&lt;/a&gt;&lt;br&gt;
Demo video: &lt;a href="https://youtu.be/-X8p4mz4Wy0" rel="noopener noreferrer"&gt;https://youtu.be/-X8p4mz4Wy0&lt;/a&gt;&lt;br&gt;
Watch how the AI breaks down a complex React issue into a 60-second roadmap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub Link: &lt;a href="https://github.com/Boweii22/First-PR-Concierge" rel="noopener noreferrer"&gt;https://github.com/Boweii22/First-PR-Concierge&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I wanted the app to be lightning-fast and accessible, so I chose a modern, high-performance stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Framework: Next.js 15 for seamless server-side rendering.&lt;/li&gt;
&lt;li&gt;Styling: Tailwind CSS + Shadcn/UI to give it a professional, trustworthy "SaaS" aesthetic that judges love.&lt;/li&gt;
&lt;li&gt;Intelligence: Gemini API (or OpenAI) to power the "Issue De-mystifier".&lt;/li&gt;
&lt;li&gt;Data: GitHub REST API to fetch live issues and user profile data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hardest part? Fine-tuning the AI prompt to ensure it didn't just give the answer but gave the guidance a mentor would provide. I wanted to make sure users still learned by doing.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Built DevConnect — Because My Developer WhatsApp Group Is a Goldmine That Keeps Forgetting Everything</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sat, 28 Feb 2026 04:29:52 +0000</pubDate>
      <link>https://forem.com/_boweii/i-built-devconnect-because-my-developer-whatsapp-group-is-a-goldmine-that-keeps-forgetting-4jom</link>
      <guid>https://forem.com/_boweii/i-built-devconnect-because-my-developer-whatsapp-group-is-a-goldmine-that-keeps-forgetting-4jom</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;I'm part of a developer WhatsApp group. Over 100 members. All time zones. Every stage — from someone writing their very first console.log to senior engineers who've shipped at scale.&lt;br&gt;
It's one of the best communities I've ever been part of.&lt;br&gt;
And it has a devastating problem.&lt;br&gt;
Someone asks a brilliant question about async/await. Three senior devs drop genuinely life-changing explanations. A resource gets shared that would have saved you six hours last week. An accountability thread starts where people share what they're building.&lt;br&gt;
Then 48 hours later?&lt;br&gt;
Gone. Buried under good morning messages, memes, and someone asking the same async/await question again.&lt;br&gt;
The knowledge was there. The people were there. But WhatsApp has no memory. Every insight, every resource, every connection — evaporates.&lt;br&gt;
I've watched this happen week after week. And this weekend, I finally did something about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;DevConnect — a dedicated community platform that acts as the organised, permanent layer on top of your existing WhatsApp group.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your WhatsApp group keeps the chat. DevConnect keeps the knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The idea is simple: your admin shares a 6-character invite code in the WhatsApp group. Members join DevConnect with that code. From that point forward, everything valuable gets posted here — where it lives forever, stays searchable, and is actually findable when you need it.&lt;br&gt;
Core Features&lt;br&gt;
📬 Four post types — because not all posts are the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❓ Need Help — post a question, get threaded replies from people who actually know the answer&lt;/li&gt;
&lt;li&gt;💡 Share — drop a resource, article, or lesson learned. It auto-saves to the Resources library&lt;/li&gt;
&lt;li&gt;🤝 Collab — find an accountability partner, a code reviewer, or someone to build with&lt;/li&gt;
&lt;li&gt;📅 Weekly Goal — post what you're working on this week. The community keeps you honest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👥 Member Directory — finally know who in your group is good at what. Filter by skill level (Beginner / Mid-level / Senior) or search by technology. No more posting "does anyone know Rust?" into the void&lt;br&gt;
📚 Resources Library — every Share post with a link automatically appears here. Tagged, searchable, permanent. The link graveyard problem: solved&lt;br&gt;
🔑 Invite-only Communities — your community stays private. Only people with your code can join. Share the code in WhatsApp, keep the knowledge in DevConnect&lt;br&gt;
❤️ Likes &amp;amp; Replies — the best answers rise. The community rewards good knowledge&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🚀 Live app: &lt;a href="https://devvconnectt.netlify.app/app" rel="noopener noreferrer"&gt;https://devvconnectt.netlify.app/app&lt;/a&gt;&lt;br&gt;
To try it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign up — you'll need an email and password to create an account. Don't want to use your real email? A throwaway like &lt;a href="mailto:test@mailinator.com"&gt;test@mailinator.com&lt;/a&gt; works perfectly fine&lt;/li&gt;
&lt;li&gt;Click Create a Community and set up your profile (name, bio, skill level, skills)&lt;/li&gt;
&lt;li&gt;Share the invite code with a friend (or open a second browser tab, sign up with a different email, and join with it)&lt;/li&gt;
&lt;li&gt;Start posting — try all four post types&lt;/li&gt;
&lt;li&gt;Check the Member Directory and Resources Library&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Github Link : &lt;a href="https://github.com/Boweii22/devconnect" rel="noopener noreferrer"&gt;https://github.com/Boweii22/devconnect&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The Stack &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcd81lbglsusn7vvg33i3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcd81lbglsusn7vvg33i3.png" alt=" " width="794" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Architecture&lt;br&gt;
The frontend talks exclusively to the Express API — never directly to Supabase. The API uses the Supabase service-role key server-side, so the database is never exposed to the client. Every route is protected by JWT middleware that verifies the token against Supabase Auth before anything happens.&lt;br&gt;
The invite code system works properly: codes are generated server-side, stored in the database, and validated on join. If you enter a code that doesn't exist, you get an error. If you've already joined that community, it handles that gracefully too.&lt;/p&gt;

&lt;p&gt;Database Schema &lt;br&gt;
Six tables cover everything:&lt;br&gt;
profiles          → extends auth.users (name, bio, level, skills[])&lt;br&gt;
communities       → name, description, invite_code (unique), creator_id&lt;br&gt;
community_members → community_id + user_id (unique pair)&lt;br&gt;
posts             → type, title, body, link, tags[], author_id, community_id&lt;br&gt;
replies           → post_id, author_id, text&lt;br&gt;
post_likes        → post_id + user_id (unique pair, toggleable)&lt;/p&gt;

&lt;p&gt;Row-level security enabled on all tables. Indexes on every foreign key and the posts feed query.&lt;/p&gt;

&lt;p&gt;The Honest Part&lt;br&gt;
The bit that actually stumped me longest wasn't the database schema or the auth flow. It was the invite code join logic — specifically handling the case where someone joins, leaves the tab open, and tries to join again from a different window. The server needs to check membership, return the community either way, and not throw a duplicate key error. Tiny edge case, genuinely fiddly to get right. But getting it right is what separates something that looks like a real app from something that is one.&lt;/p&gt;

&lt;p&gt;The community this was built for has been running for years on WhatsApp. The knowledge has always been there. The people have always been there. This weekend I just gave it somewhere to live.&lt;br&gt;
If your dev group has the same problem — try it. The invite code is the same one you'd drop in your WhatsApp chat.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>We Built Voice Chat That Lives Entirely in Your Terminal (Yes, Really)</title>
      <dc:creator>Tombri Bowei</dc:creator>
      <pubDate>Sun, 15 Feb 2026 01:18:21 +0000</pubDate>
      <link>https://forem.com/_boweii/we-built-voice-chat-that-lives-entirely-in-your-terminal-yes-really-3i9k</link>
      <guid>https://forem.com/_boweii/we-built-voice-chat-that-lives-entirely-in-your-terminal-yes-really-3i9k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What we Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;VoiceSync&lt;/strong&gt; — because who needs a GUI when you can have hacker vibes? 😎&lt;/p&gt;

&lt;p&gt;We built a fully functional voice chat app that runs entirely in your terminal. It's like Discord and Zoom had a baby and raised it on command-line aesthetics. &lt;/p&gt;

&lt;p&gt;Here's what makes it actually sick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎙️ &lt;strong&gt;Real-time voice chat&lt;/strong&gt; with live waveform visualization (you can literally SEE sound waves)&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;End-to-end encryption&lt;/strong&gt; (AES-256-GCM + Diffie-Hellman because we're not savages)&lt;/li&gt;
&lt;li&gt;💬 &lt;strong&gt;Text chat + desktop notifications&lt;/strong&gt; (so you know when your friend roasts you)&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;Works over the internet&lt;/strong&gt; via ngrok tunnels&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;Built-in AI assistant&lt;/strong&gt; powered by GitHub Copilot CLI (yeah, Copilot inside Copilot; we went there)&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Audio quality indicators&lt;/strong&gt; showing latency, bitrate, and packet jitter in real-time&lt;/li&gt;
&lt;li&gt;👥 &lt;strong&gt;Join/leave notifications&lt;/strong&gt; so you know who's lurking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt; Node.js, WebSockets, SoX (for audio), Blessed.js (for the terminal UI), and a concerning amount of energy drinks. &lt;br&gt;
&lt;strong&gt;Shoutout to my team:&lt;/strong&gt; &lt;a class="mentioned-user" href="https://dev.to/muhammedameen_enesiibrah"&gt;@muhammedameen_enesiibrah&lt;/a&gt; and &lt;a class="mentioned-user" href="https://dev.to/thecodedaniel"&gt;@thecodedaniel&lt;/a&gt;  — couldn't have debugged that audio echo nightmare without y'all 🙏&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Screenshots to Add:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ltw06vx6mjtcynela1o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ltw06vx6mjtcynela1o.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tdzm8oodzpa6d91efkw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tdzm8oodzpa6d91efkw.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkp7yyyj9s1diz1vl44xr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkp7yyyj9s1diz1vl44xr.png" alt=" " width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzi9o7cc37jesy95jy1je.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzi9o7cc37jesy95jy1je.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub Repo:&lt;/strong&gt; [&lt;a href="https://github.com/Boweii22/Copilot" rel="noopener noreferrer"&gt;https://github.com/Boweii22/Copilot&lt;/a&gt;]&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Live Demo Recording:&lt;/strong&gt; [&lt;a href="https://youtu.be/vtDgEqb9GcU" rel="noopener noreferrer"&gt;https://youtu.be/vtDgEqb9GcU&lt;/a&gt;]&lt;/p&gt;
&lt;h2&gt;
  
  
  My Experience with GitHub Copilot CLI
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Good Stuff
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before Copilot:&lt;/strong&gt; "How do I encrypt WebSocket audio streams again?"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;After Copilot:&lt;/strong&gt; &lt;code&gt;gh copilot suggest "encrypt websocket audio stream nodejs"&lt;/code&gt; → instant answers&lt;/p&gt;

&lt;p&gt;Copilot CLI was basically our third brain. We integrated it INSIDE the app so users can literally ask questions while using VoiceSync:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[You]: &lt;a class="mentioned-user" href="https://dev.to/copilot"&gt;@copilot&lt;/a&gt; how does encryption work in this app?&lt;br&gt;
[Copilot]: VoiceSync uses AES-256-GCM encryption with Diffie-Hellman key exchange...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Meta? Yes. Useful? Absolutely.&lt;/p&gt;
&lt;h3&gt;
  
  
  Real Scenarios Where Copilot Saved Us
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Audio Echo Bug from Hell&lt;/strong&gt;
We spent 3 days with the host hearing themselves twice. Copilot suggested checking if the server was broadcasting to itself. One line fix. Pain = over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;gh copilot explain "websocket server broadcasting to sender"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SoX Cross-Platform Nightmare&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Windows uses &lt;code&gt;-t waveaudio&lt;/code&gt;, Mac uses &lt;code&gt;-d&lt;/code&gt;, Linux uses... something else? Copilot helped us write a config module that detects the platform and uses the right args.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gh copilot suggest "detect platform and use correct audio device args nodejs"&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminal UI Rendering Issues
Text was bleeding across panels like a cursed PowerPoint. Asked Copilot about Blessed.js batched rendering and it explained we were calling screen.render() too many times. Boom, fixed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The "Oh Damn" Moment
&lt;/h2&gt;

&lt;p&gt;The coolest part? We used Copilot so much during development that we thought: "What if users could do this too?" So we added &lt;a class="mentioned-user" href="https://dev.to/copilot"&gt;@copilot&lt;/a&gt; as an in-app command. Now anyone using VoiceSync can ask questions without leaving the terminal. It's like having a dev team on standby.&lt;/p&gt;
&lt;h2&gt;
  
  
  Productivity Boost
&lt;/h2&gt;

&lt;p&gt;Honestly? We probably saved 40-50 hours.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more alt-tabbing to Stack Overflow&lt;/li&gt;
&lt;li&gt;No more "wait let me Google that"&lt;/li&gt;
&lt;li&gt;No more deciphering cryptic error messages alone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot CLI became our debugging buddy, our documentation search engine, and our rubber duck all in one.&lt;/p&gt;
&lt;h2&gt;
  
  
  What We Learned
&lt;/h2&gt;

&lt;p&gt;GitHub Copilot CLI isn't just for writing code — it's for understanding code. When we hit errors, instead of rage-Googling, we'd just:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gh copilot explain "SyntaxError: Unexpected token in JSON"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And it'd tell us we were trying to parse binary audio frames as JSON (which... yeah that makes sense now).&lt;/p&gt;

&lt;p&gt;Try It Yourself&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install
git clone https://github.com/Boweii22/Copilot
npm install

# Host a room
npm run host

# Join from another terminal/machine
npm run join -- localhost ABC123 YourName
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full instructions in the README. Works on Windows, Mac, and Linux. Requires Node.js and SoX (we documented EVERYTHING because we're not monsters).&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;We built this in a very short amount of time so it was quite a bit of disturbing stress 😭😅 while juggling sleep deprivation. GitHub Copilot CLI was the fourth team member we didn't know we needed. If you've ever thought "I wish I could ask my terminal for help" — you can now.&lt;/p&gt;

&lt;p&gt;Also if you're still using Discord for voice calls with friends, you're missing out on the superior terminal experience. Come @ us. 😤&lt;/p&gt;

&lt;p&gt;Built with: Way too much sweets, GitHub Copilot CLI, and the power of friendship (and WebSockets 😅)&lt;br&gt;
P.S. — Yes, we know P2P would be cooler. That's v2.0. For now, enjoy self-hosted voice chat that actually works.&lt;br&gt;
Team: &lt;a class="mentioned-user" href="https://dev.to/_boweii"&gt;@_boweii&lt;/a&gt; , &lt;a class="mentioned-user" href="https://dev.to/muhammedameen_enesiibrah"&gt;@muhammedameen_enesiibrah&lt;/a&gt; , &lt;a class="mentioned-user" href="https://dev.to/thecodedaniel"&gt;@thecodedaniel&lt;/a&gt; &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
  </channel>
</rss>
