<?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>DEV Community: Sakshi Shanbhag</title>
    <description>The latest articles on DEV Community by Sakshi Shanbhag (@sakshi_shanbhag_446bade8f).</description>
    <link>https://dev.to/sakshi_shanbhag_446bade8f</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%2F3840289%2Fbdfeb871-f0a0-4732-9ad0-11277879643f.jpg</url>
      <title>DEV Community: Sakshi Shanbhag</title>
      <link>https://dev.to/sakshi_shanbhag_446bade8f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sakshi_shanbhag_446bade8f"/>
    <language>en</language>
    <item>
      <title>Why Hindsight Replaced My Manual Reminders</title>
      <dc:creator>Sakshi Shanbhag</dc:creator>
      <pubDate>Mon, 23 Mar 2026 18:24:38 +0000</pubDate>
      <link>https://dev.to/sakshi_shanbhag_446bade8f/why-hindsight-replaced-my-manual-reminders-2oco</link>
      <guid>https://dev.to/sakshi_shanbhag_446bade8f/why-hindsight-replaced-my-manual-reminders-2oco</guid>
      <description>&lt;p&gt;Last night I Daydreamed about an AI that could quietly nudge a student team back on track. By morning, it had already happened: Hindsight had analyzed our overdue tasks and sent personalized, context‑aware reminders to every teammate who’d stalled.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What We Built&lt;/strong&gt;&lt;br&gt;
We started as a weekend experiment: a project‑management tool with a real AI backbone — not just another GPT‑4 wrapper with some canned prompts. I wanted a system that could assign tasks intelligently, summarize meetings on its own, track how the team performed over time, and actually remember what happened in the last sprint. Not a Jolt‑or‑Jira clone. Something that gets smarter the longer you use it.&lt;/p&gt;

&lt;p&gt;The result is a FastAPI backend with nine distinct routers — &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt;, &lt;code&gt;teams&lt;/code&gt;, &lt;code&gt;tasks&lt;/code&gt;, &lt;code&gt;decisions&lt;/code&gt;, &lt;code&gt;ai&lt;/code&gt;, &lt;code&gt;meetings&lt;/code&gt;, &lt;code&gt;integrations&lt;/code&gt;, and &lt;code&gt;reports&lt;/code&gt; — backed by a SQLite database managed through SQLAlchemy 2.0, and a React + Vite frontend running on :5173. The core AI calls go to either Groq (fast, cheaper inference) or OpenAI depending on the task.&lt;br&gt;
But the piece that makes the whole thing coherent — the thread that connects last‑sprint decisions to today’s recommendations — is &lt;strong&gt;Hindsight&lt;/strong&gt;.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Moving Parts&lt;/strong&gt;&lt;br&gt;
Here’s the rough shape of the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text
Frontend (React + Vite)
    │
    ▼
FastAPI (main.py) → 9 routers
    │
    ├── SQLite (SQLAlchemy ORM)      ← persistent state
    ├── Groq / OpenAI APIs           ← LLM inference
    └── Hindsight client             ← agent memory layer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend is split into &lt;code&gt;routes&lt;/code&gt; (HTTP handlers) and &lt;code&gt;services&lt;/code&gt;(business logic). The services layer — &lt;code&gt;suggestion_service&lt;/code&gt;, &lt;code&gt;insights_service&lt;/code&gt;, &lt;code&gt;chat_service&lt;/code&gt;, &lt;code&gt;task_service&lt;/code&gt;, &lt;code&gt;team_service&lt;/code&gt;, &lt;code&gt;auth_service&lt;/code&gt; — is where most of the interesting stuff lives. The routes are deliberately thin: they parse requests, call services, and return responses. Standard FastAPI pattern, nothing surprising.&lt;/p&gt;

&lt;p&gt;The real story lives in &lt;code&gt;db_models.py&lt;/code&gt; — eleven SQLAlchemy models: &lt;code&gt;DBTeamMember&lt;/code&gt;, &lt;code&gt;DBTask&lt;/code&gt;, &lt;code&gt;DBDecision&lt;/code&gt;, &lt;code&gt;DBMeeting&lt;/code&gt;, &lt;code&gt;DBMeetingParticipant&lt;/code&gt;, &lt;code&gt;DBMeetingTranscript&lt;/code&gt;, &lt;code&gt;DBMeetingRecording&lt;/code&gt;, &lt;code&gt;DBMeetingSummary&lt;/code&gt;, &lt;code&gt;DBIntegration&lt;/code&gt;, &lt;code&gt;DBUserIntegration&lt;/code&gt;, and &lt;code&gt;DBAutomationRule&lt;/code&gt;. That last one — &lt;code&gt;DBAutomationRule&lt;/code&gt; — is where the “magic” begins.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Memory Problem&lt;/strong&gt;&lt;br&gt;
When I first built the task‑assignment logic, it was stateless. A manager types “Implement OAuth2 login,” and the system suggests Alice because her skills column says “Backend, Python, API.” That works — until Alice is slammed, or until she’s spent two sprints struggling with auth‑related tickets. The system has no memory of that.&lt;/p&gt;

&lt;p&gt;My first instinct was to write a pile of SQL queries: track completion rates, flag delays, compute a score per member per skill category. I started down that road and ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DBTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;task_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assigned_to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To Do&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# To Do, In Progress, Completed
&lt;/span&gt;    &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ai_rationale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;confidence_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ai_rationale&lt;/code&gt; and &lt;code&gt;confidence_score&lt;/code&gt; columns were the key addition. Every time the system assigns a task, it stores why — a plain‑text rationale plus a numeric confidence. That gave us an audit trail, but it still didn’t solve the memory problem. If the system suggests Alice for a backend task, saves confidence_score=85, and then she misses the deadline, that outcome is recorded in the tasks table — but the next assignment cycle never reads it or learns from it.&lt;/p&gt;

&lt;p&gt;That’s where Hindsight stepped in.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What Hindsight Actually Does&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Hindsight&lt;/code&gt; is a memory layer for AI agents. You push events to it — task completions, delays, meeting outcomes, team decisions — and it stores them in a vector index. When your agent needs to make a decision, it queries that index and pulls relevant past episodes as context. The LLM sees what happened last time, not just what the current state is.&lt;/p&gt;

&lt;p&gt;The integration in this project is a single &lt;code&gt;pip install hindsight-client&lt;/code&gt; line in &lt;code&gt;requirements.txt&lt;/code&gt; and an API key in &lt;code&gt;.env&lt;/code&gt;.&lt;br&gt;
Every event that matters — task completion, a missed deadline, a key architectural decision — gets written to Hindsight. When the suggestion_service runs, it pulls retrieval context from Hindsight before hitting the LLM. The prompt might look like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Past context from memory:&lt;br&gt;
Alice completed “Build setup database API backend” on time (difficulty: Hard)&lt;br&gt;
Alice delayed “Fix UI dashboard bug” by 1 day (out‑of‑skill assignment)&lt;br&gt;
Bob struggled with “Migrate database schema” (backend task for frontend dev)&lt;br&gt;
Current request: Assign “Implement caching layer for API endpoints”&lt;br&gt;
Team members: Alice (Backend, Python), Bob (Frontend, React), Charlie (DevOps, QA)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That past context is what turns a skill‑keyword match into a genuinely useful recommendation. Without it, you’d assign Bob “Migrate database schema” because he’s on the team and available. With it, you know Bob already tried that once and it went badly.&lt;/p&gt;

&lt;p&gt;The agent memory page on Vectorize describes this pattern more formally: agents need episodic memory to improve over time, rather than starting fresh on every decision.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Decisions as First‑Class Citizens&lt;/strong&gt;&lt;br&gt;
One thing I’m proud of: the system treats architectural decisions as data, not just meeting notes in a Google Doc somewhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DBDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decisions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# Full decision text
&lt;/span&gt;    &lt;span class="n"&gt;decided_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# User/team who made the decision
&lt;/span&gt;    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;General&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 'Architecture', 'Process', 'Hiring'
&lt;/span&gt;    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The seed data shows exactly what we needed this for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DBDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Freeze Friday Deployments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;After 3 incidents, team agreed: no production deployments on Fridays. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Releases to happen Tuesday–Thursday only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;decided_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Charlie&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Process&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That decision lives in the database and gets pushed to Hindsight. Now when someone asks AI ProPilot in the chat, “Why can’t we deploy today?”, the system retrieves that exact episode and answers with the real reasoning — not a hallucination, not a lookup in a static FAQ, but the actual decision made by Charlie after three real incidents. The &lt;code&gt;chat_service&lt;/code&gt; is what drives this: it queries Hindsight, builds a grounded prompt, and sends it to the LLM.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Meeting Memory: The Transcript Pipeline&lt;/strong&gt;&lt;br&gt;
The meeting system started as an afterthought and became one of the most useful slices of the product. The schema covers the full lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DBMeeting&lt;/code&gt; — the calendar event&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DBMeetingParticipant&lt;/code&gt; — who attended&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DBMeetingTranscript&lt;/code&gt; — speaker‑attributed transcript lines&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DBMeetingRecording&lt;/code&gt; — URL + duration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DBMeetingSummary&lt;/code&gt; — AI‑generated overview, key points, action items, next steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;DBMeetingSummary.action_items&lt;/code&gt; column is stored as a JSON‑stringified array of &lt;code&gt;{ "task": "...", "assignee": "..." }&lt;/code&gt; objects. After every meeting, the system parses the transcript, calls the LLM to extract action items, and writes them back. Those action items then feed into the task‑creation pipeline.&lt;/p&gt;

&lt;p&gt;The uncomfortable part: I store JSON directly in a &lt;code&gt;String&lt;/code&gt; column rather than a proper join table. It’s faster to query, harder to index. For a team of three, it’s fine. For fifty people with hundreds of meetings, you’d want a real &lt;code&gt;action_items&lt;/code&gt; table with foreign keys. That’s a known limitation.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;The Automation Rule Layer&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;DBAutomationRule&lt;/code&gt; model is the infrastructure that makes the overnight reminders from the opening hook actually work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DBAutomationRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;automation_rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;trigger_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 'TASK_COMPLETED', 'MEETING_SCHEDULED', 'DEADLINE_MISSED'
&lt;/span&gt;    &lt;span class="n"&gt;action_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 'CHAT_NOTIFY', 'CREATE_TASK', 'SEND_REMINDER'
&lt;/span&gt;    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# JSON: trigger/action parameters
&lt;/span&gt;    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rule like &lt;code&gt;trigger: DEADLINE_MISSED → action: SEND_REMINDER&lt;/code&gt; is what drives the nightly reminders. When the scheduler sees an overdue task, it fires the rule, pulls the assignee’s history from Hindsight, and generates a context‑aware message — not “hey your task is late,” but:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hey, you’ve completed 8 of 9 backend tasks on time; this UI bug has been stalled for 2 days. Want to pair with Bob on it?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;config&lt;/code&gt; column is JSON‑in‑a‑string again. Same tradeoff as the action items.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What Actually Worked, What Didn’t&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What worked&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ai_rationale&lt;/code&gt; &lt;strong&gt;on every task assignment&lt;/strong&gt;. Storing the reasoning at creation time is underrated. When you review assignments a week later, you understand exactly why a decision was made — and so does AI ProPilot when it queries the history.
-** Hindsight as the connective tissue.** Writing events to a vector memory layer and retrieving them at inference time is simple to implement and has a disproportionate impact on response quality. The LLM gives better answers when it has real episodes to reason from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI’s dependency injection for auth.&lt;/strong&gt; &lt;code&gt;Depends(auth_service.get_current_user)&lt;/code&gt; is clean and composable. Protected endpoints never forget to authenticate because the pattern makes it structural, not manual.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Treating decisions as first‑class data.&lt;/strong&gt; The &lt;code&gt;DBDecision&lt;/code&gt; table with &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;decided_by&lt;/code&gt;, and &lt;code&gt;content&lt;/code&gt; gives the chat system real grounding material.&lt;/p&gt;

&lt;p&gt;What didn’t work as expected&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON in &lt;code&gt;String&lt;/code&gt;columns&lt;/strong&gt;. Fast to ship, painful to query. If you need to filter action items by assignee, you can’t do it in SQL — you have to pull everything into Python and filter in memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groq for long meeting transcripts&lt;/strong&gt;. Groq is fast, but token‑window constraints forced us to chunk long transcripts before summarizing. OpenAI was more reliable for full‑session summaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite under concurrent load.&lt;/strong&gt; check_same_thread=False gets you far, but write contention becomes visible once multiple automation rules fire simultaneously. Postgres with connection pooling is the right move for anything beyond a small team.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Rules I’d Apply Next Time&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Store LLM rationale alongside every AI decision from day one.&lt;/strong&gt; It costs nothing and makes the system auditable and debuggable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a dedicated memory layer early&lt;/strong&gt;— Hindsight or something similar. Don’t wait until you’ve hand‑rolled five SQL queries that approximate retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON‑in‑a‑string columns are a smell.&lt;/strong&gt; It’s fine for rapid prototyping, but plan the migration to proper tables before you have real production data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation rules need idempotency.&lt;/strong&gt; last_triggered is a start, but rules that fire twice on the same event create duplicate reminders. Worth designing a proper event‑deduplication mechanism upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns between routes and services pays off immediately.&lt;/strong&gt; The routes in this project are genuinely thin — they just call service functions. When I needed to rewrite the suggestion logic, I touched one file.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What This Thing Has Become&lt;/strong&gt;&lt;br&gt;
AI ProPilot now handles task assignment, meeting summaries, team insights, and overnight reminders — and the context‑awareness of those outputs is entirely a function of the memory layer. Without Hindsight writing and reading episodic events, it’s just another CRUD app with an LLM bolted on. With it, the agent actually behaves like it knows your team, remembers your past sprints, and gently nudges you back on track — often before you notice something’s gone astray.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>machinelearning</category>
      <category>fastapi</category>
    </item>
  </channel>
</rss>
