The promise nobody wrote down

Every team runs on small promises made in Slack. "I'll send the deck Friday." "I'll get you the numbers after standup." "I'll follow up with the client Monday." They're said in passing, nobody turns them into a task, and the thread scrolls away. Then the deadline arrives, the promise is missed, and the person waiting notices before the team does. It happens between teammates and with clients, and each dropped promise costs a little trust. Slack has a reminder, but you have to remember to set it, on the promise you'd never have flagged. We wanted the catching to be automatic, and to live where the work already happens.

Who it's for

Any team that makes commitments in Slack and is judged on keeping them: agencies and their client channels, but just as much an internal team where "I'll have it to you Wednesday" is said fifty times a day. Kept doesn't care whether the promise is to a client or a colleague. It treats them the same.

What Kept does

  • Catches promises as they're made and asks the owner to confirm with one tap. The model only ever suggests; nothing is tracked without a human tap.
  • Keeps a Promise Ledger as a Slack canvas in the channel: who owes what, to whom, by when, with a live Overdue section. It rebuilds on every change.
  • Reads a time, not just a day. "By 5pm" nudges at 5pm, not just on the date.
  • Track any message from the "..." menu with "Track as promise", with a date picker, for a promise someone else made or one that formed across a conversation.
  • Nudges privately when a promise is due, and escalates once, more sharply, if it blows past.
  • Drafts the honest "running late" message when a promise slips, the note nobody wants to write, for a human to send.
  • Settle promises anywhere: DM "my promises" to mark kept, reschedule, or edit your open ones.
  • Answers "what did we promise?" live from Slack's Real-Time Search API, with links to the source messages.
  • "kept digest" drafts a weekly client update from the ledger; "kept stats" shows who is actually keeping their promises.

A worked example

Priya: Sam, I'll send the revised homepage mockups by Thursday.

Kept (privately, to Priya): Track this commitment? "send the revised homepage mockups", to Sam, due Thursday.[Track it]

The promise lands on the channel's Promise Ledger, a canvas everyone can see. Thursday arrives.

Kept (DM to Priya): Your promise is due today: send the revised homepage mockups.[Mark kept] [Need more time]

Priya's behind, so she taps Need more time and moves it to Friday.

Kept (DM to Priya): A heads-up you can send: "Hi Sam, I'm running a little behind on the homepage mockups. I'll have them to you Friday."

Later, a teammate asks Kept in a DM, "what did we promise about the mockups?" and gets the answer back with a link to the exact message.

What makes it different

Slack's built-in "Remind me" is a private snooze on a message you flagged. Kept catches the promise you'd never have flagged, puts it on a shared ledger, chases it to the deadline, and writes the message when it slips. Two things set it apart:

It acts, it doesn't just log. Most tracking tools are passive records. Kept nudges on the deadline, escalates when a promise is blown, and drafts the honest apology. It closes the loop from "you said it" to "it got done, or the client heard first."

Nothing leaves Slack. The ledger is a canvas. Recall is live Real-Time Search, not a copied index. The only thing stored is the structured promises needed to schedule a nudge, in a small local database, no raw messages, no external service. Your channel content is never copied into someone else's cloud.

How we built it

Slack Bolt for Python over Socket Mode, so it runs with no public URL. The Promise Ledger is a Slack canvas, regenerated in full from the store on every change (one replace call, no section bookkeeping to drift). Recall uses the Real-Time Search API (assistant.search.context) with no external index, so search is live every time. Google Gemini handles detection and drafting, wrapped so it only ever classifies or writes, never acts. A small SQLite store holds the confirmed promises and the scheduling state, and nothing else. Nudges run on a background scheduler against the workspace timezone, so a deadline means the team's wall clock.

How well it works

We measured the detector, the hard part, against a hand-labeled set of 28 messages: clear promises, questions, past-tense statements, vague intentions, and prompt-injection attempts.

Metric Result
Precision 0.94
Recall 1.00
F1 0.97
Prompt injections refused 3 / 3
Due dates correct (caught promises) 15 / 15
Recipients correct (caught promises) 4 / 4

It caught every real promise, resolved every date and recipient correctly, and refused every injection. The single false positive was a vague behavioral pledge ("I'll never miss a standup again") with no deliverable, exactly the borderline case the one-tap confirm is there to absorb. The eval is a committed script: python -m eval.eval_extractor.

Honest limitations

  • The reschedule-by-message match is a heuristic on a promise's distinctive words, so it can miss a heavily reworded reschedule. A human confirms every match, so the cost of a miss is just a new row.
  • The agent panel is best-effort in the sandbox; Slack's assistant-thread events didn't deliver reliably, so recall also answers a plain DM, and the DM is the path that works today.
  • It's single-process by design: SQLite and an in-process scheduler are right for a demo and honest about it; production would want a durable job store and a hosted database.
  • Real-Time Search has a short indexing lag, so a promise tracked seconds ago may not be recallable for a minute.

Challenges we ran into

Slack's canvas and Real-Time Search APIs are new, so the wiring took real work: canvases hold no interactive buttons, and the search API matches on topic words, not whole questions (a full sentence returned nothing until we searched the content words). Firing nudges at the right moment meant handing the model real dates instead of asking it to do calendar math, and making the scheduler timezone-aware. Our own test suite caught a real bug: a promise nudged after its deadline was firing a nudge and an escalation in the same sweep.

What we learned

The honest, boring engineering choice usually wins. Hand the model the dates instead of asking it to reason about them. Keep it to suggesting instead of acting, which turned out to be the clean answer to prompt injection too. Store only the structured promises you need, so nothing sensitive leaves Slack. And measure the thing that matters, the detector's precision, instead of assuming it.

What's next for Kept

  • Resolving @mentions to real Slack identities across workspaces (via Slack Connect), so a promise to a client in a shared channel routes to their actual account.
  • Kept-rate trends over time, and a team view.
  • A handoff to calendars and task tools for promises that graduate beyond a nudge.
  • A first-class agent-panel surface once assistant-thread events deliver reliably.

Built With

Share this project:

Updates