Inspiration
Every one of us has a mental list of things that are technically "in progress" but actually stuck. You messaged a teammate about a file and never heard back. You can't finish the report until the analysis is done, and you can't do the analysis until that file arrives. Nothing is late yet, so no to-do app complains — but the task is quietly dead in the water, and you're the only system holding the thread.
We called these open loops. Regular task managers are terrible at them, because a checkbox can only store what you have to do. It can't store who you're blocked on, what has to happen first, or what the next real move is right now.
So we built Unloop — an AI that remembers. You describe the mess in plain English, and Unloop turns it into a structured loop with a person, a blocker, a dependency chain, a deadline, and one concrete next action
What it does
You type or speak something like:
"I need to finish the HIA project report by Friday but I'm waiting for the survey data from Priya before I can complete the analysis and submit the report."
Unloop reads that one sentence and extracts:
Task — "Finish HIA project report" Person — Priya Waiting for — survey data Dependency chain — survey data → complete analysis → submit report Deadline — Friday Category — Academic Status — Waiting Next action — "Follow up with Priya" Priority — computed, not guessed From there the app gives you:
Home / Attention feed — loops ranked by how much they actually need you today, each with a plain-English reason ("You're waiting for Priya. A follow-up may unblock Finish HIA project report.") Loops — every commitment you've captured, with status, category and due date Waiting For — a dedicated view of everything blocked on another human, so nothing dies in someone else's inbox Insights — totals across Active / Waiting / Completed and Academic / Work / Personal Loop detail — auto-generated steps from the dependency chain, plus Details and Activity tabs Smart notifications — the backend scans open loops and writes nudges, with de-duplication so you don't get told the same thing twice in a day Voice + file capture — dictate a loop with the Web Speech API, or upload a .txt braindump Google Calendar — OAuth read-only sync so upcoming events sit next to your open loops
How we built it
Frontend — React 19 + Vite A single-page app with a sidebar shell and five pages (Home, Loops, Waiting For, Insights, You). Everything is hand-built: a custom inline SVG Icon component, a custom Logo, and ~2,400 lines of CSS with no UI framework. The add-loop flow is deliberately two-step — you write freely, Unloop shows you an Understanding card of what it parsed, and only then do you save. The AI never silently guesses on your behalf.
Backend — Express 5 A REST API exposing /api/understand, /api/loops, /api/attention, /api/insights, /api/notifications, and the Google Calendar routes.
The understanding engine Rather than reaching for an LLM call on every keystroke, we wrote a deterministic NLP pipeline out of layered regex extractors:
makeCleanTask() — strips filler like "I need to…" / "I have to…" and truncates at natural boundaries (by, but, because, once) to leave a clean imperative title extractPerson() — pulls a human name out of "waiting for X" / "follow up with X" / "ask X", filtered through a blocklist of verbs so "Check delivery" never becomes a person named Check extractWaitingFor() — isolates the blocking artifact and trims trailing clauses extractDeadline() — weekdays, relative dates, and "12th December" style dates detectCategory() — Academic / Work / Personal via keyword sets buildDependency() — assembles the A → B → C chain
The reasoning engines Three pure functions do the actual thinking:
calculateNextAction() — resolves the single next move (follow up with the person, chase the blocker, or take the head of the dependency chain) calculatePriority() — an additive score where being blocked on a person, having a real deadline, and carrying a dependency chain each add weight, then buckets into P1 / P2 / P3 generateAttention() / attentionFor() — produces the human-readable why behind every ranking
The priority score, roughly:
score
3 ⋅ 1 waiting + 2 ⋅ 1 person + 2 ⋅ 1 blocker + 3 ⋅ 1 deadline + 2 ⋅ 1 dependency + 1 academic score=3⋅1 waiting
+2⋅1 person
+2⋅1 blocker
+3⋅1 deadline
+2⋅1 dependency
+1 academic
with ( \text{score} \geq 7 \rightarrow P1 ) and ( \text{score} \geq 4 \rightarrow P2 ).
Database — SQLite via better-sqlite3 Three tables (loops, notifications, calendar_events) in WAL mode. The schema file runs a self-migration on boot: it reads PRAGMA table_info(loops) and ALTER TABLEs in any missing column, so an older unloop.db upgrades itself instead of crashing.
javascript
for (const [column, type] of Object.entries(requiredColumns)) {
if (!columns.includes(column)) {
db.exec(ALTER TABLE loops ADD COLUMN ${column} ${type});
}
}
Integrations — googleapis OAuth2 for read-only Calendar access, and the browser-native Web Speech API for voice capture.
##Challenges we ran into
Names are hard. Our first person-extractor confidently decided "Check" and "Finish" were people, because "check delivery status" matches the same shape as "contact Priya". We ended up needing a verb blocklist plus capitalisation-aware patterns.
Knowing where a task ends. "I need to finish the report by Friday but I'm waiting on Priya" — a naive capture swallows the whole sentence as the title. Writing lookaheads that cut cleanly at by / but / because / once without amputating legitimate task text took a lot of iteration.
Priority is subjective. Our first version just sorted by deadline, which buried every blocked-on-a-human task because those usually have no date. Rebuilding it as an additive signal score — where being blocked is itself urgent — was the change that made the Home feed feel genuinely smart.
Notification spam. The first /api/notifications/check regenerated identical rows on every poll. We added a SQL guard checking for the same loop_id + message within datetime('now', '-1 day').
Keeping two brains in sync. Both the server and the client can compute next-action and attention text (so the UI still reasons if the API is slow). Keeping those two implementations consistent was a constant discipline.
Token storage. Calendar OAuth tokens live in global.googleTokens — fine for a prototype, and a known limitation we've documented rather than hidden.
##Accomplishments that we're proud of A working NLP pipeline with zero API cost and zero latency — it runs offline, instantly, and the same input always gives the same output Dependency chains that turn a rambling sentence into an ordered, checkable sequence of steps An attention feed that always explains why something surfaced, instead of just showing a red badge A fully custom interface — every icon, the logo, and all styling written by hand A database that migrates itself ##What we learned Deterministic beats clever, sometimes. We assumed we'd need an LLM. For this bounded domain, layered regex with good boundary conditions was faster, free, debuggable and reproducible. Order of operations matters in NLP. Clean the title before pattern-matching on it, and check specific patterns before general ones, or the general ones eat everything. Users need to see the AI's reasoning. The Understanding card — showing exactly what was parsed before saving — turned out to be the single most trust-building feature in the app. The data model is the product. Adding person, waiting_for and dependency as first-class columns is what makes Unloop something a to-do list fundamentally cannot be.
What's next for unloop
What's next for Unloop Optional LLM layer for messy, multi-loop paragraphs the regex pipeline can't handle — with the deterministic engine as a free, instant fallback Real date parsing so "Friday" becomes an actual timestamp that can go stale, and overdue loops escalate on their own Two-way Calendar sync — write blocked-out focus time straight back into Google Calendar Persistent, per-user OAuth tokens and real authentication Email and chat ingestion so a loop opens automatically the moment you send "can you send me the file?" Follow-up drafting — one tap generates the nudge message to the person you're blocked on Loop graphs — visualise how commitments chain across people, and spot the one blocker holding up five things Mobile app with ambient reminders that fire on context, not just on a clock
Built With
- better-sqlite3
- cors
- css3
- dotenv
- express.js
- google-calendar-api
- googleapis
- javascript
- node.js
- oauth2
- react
- rest-api
- sqlite
- vite
- web-speech-api
Log in or sign up for Devpost to join the conversation.