Inspiration
Every engineering org has already solved most of its incidents. The fix is buried in a Slack thread somebody wrote weeks ago, and that person is asleep. So at 2am the on-call engineer solves it again, from scratch.
There are already tools that resurface the old thread. That is the easy half, and on its own it is dangerous, because the code has moved on. A fix that worked in July can be refactored away in September. Handing a tired responder a stale patch, confidently, is worse than handing them nothing.
So I didn't want to build another memory bot. I wanted one that goes and checks whether the old fix survived.
What it does
Rewynd watches #incident-response. When someone posts a stack trace, it:
- Finds the thread where your team fixed this before. Who resolved it, when, and the fix they linked.
- Grounds it in your live code. Through the GitHub MCP server it fetches the
file:linethat actually threw, resolves the commit from the era of that past fix, and diffs the implicated region. - Delivers a verdict.
✅ Seen before · fixed by @trish · current code MATCHES. The fix still applies.⚠️ Seen before · fixed by @trish · code has since DRIFTED. The old fix may not apply.
- Reconciles the past fix against the current code and writes a recommendation for the code as it exists now. If it drifted, the recommendation explains why the old patch won't transfer.
- Speaks a 15 second triage summary for the responder who is heads-down in a terminal. Verdict and gist only. Every line number stays in copyable text.
- Re-captures the knowledge. One confirm-gated click opens a GitHub issue linking the old incident, the new one, and the recommendation, then annotates the thread. Nothing is written before a human clicks.
In the demo, Rewynd finds Trish's fix: she wrapped a payment-gateway call in a backoff retry to stop an ETIMEDOUT. Then it looks at that same line today and reports DRIFTED. Someone dropped her retry wrapper during an SDK migration, and the timeout came straight back.
That verdict is the product. It is the difference between institutional memory and institutional judgment.
How I built it
Bolt for JavaScript (TypeScript, Socket Mode). Four layers behind typed interfaces, chained by one orchestrator, each with an honest fallback.
MCP server integration, the required technology, doing real work. I wrote the MCP client from scratch rather than wrapping an SDK. It speaks Streamable HTTP (initialize → tools/list → tools/call) and handles both JSON and SSE responses. It drives the GitHub MCP server: get_file_contents to ground the error, list_commits to pin the fix era, and issue_write for the confirm-gated write. Tool names are discovered at runtime, because this server calls issue creation issue_write and not create_issue.
Real-Time Search API. assistant.search.context, queried as a natural-language question ("How was the ETIMEDOUT error in payments resolved before?"). Phrasing the query as a question is what triggers semantic retrieval instead of keyword matching.
Synthesis runs on an open-source LLM via an OpenAI-compatible endpoint (Groq, Llama 3.3 70B). Slack AI is a set of product features rather than a callable completion API, so Rewynd doesn't pretend to call it. Voice is Murf.
Challenges I ran into
1. A channel-only agent physically cannot use Real-Time Search.
assistant.search.context requires an action_token. Slack attaches that token to agent-surface events only. A plain channel message never carries one, no matter which scopes you hold. I only found this by dumping every raw event Slack delivered and reading the payloads.
That constraint drove the architecture. Rewynd has two front doors. In the agent DM it gets a token and runs semantic RTS. In #incident-response it falls back to workspace conversation-history search. The logs always name the path that actually ran, so it never claims a capability it didn't use.
2. The GitHub MCP returns file contents in a resource item, not a text item.
get_file_contents replies with two content items: a text status line ("successfully downloaded text file...") and a resource item whose .text holds the actual source. I read the text item, which is the obvious thing to do, and silently got a one-line "file".
My diff was then comparing two empty regions and reporting MATCH on every single incident. It looked like it worked. It didn't. Finding that is the difference between a demo and a working agent, and it is why I added a guard so an unreadable region can never be reported as a MATCH again.
3. A bare commit SHA must go in sha, not ref.
ref wants a fully-qualified refs/heads/.... Pass a raw SHA and the server quietly hands back the default branch, so you end up diffing main against main.
What I learned
Most of the work wasn't writing the agent. It was discovering the platform's actual shape. None of the three things above are in the docs, and each one failed silently, which is the worst way for a system to fail: the demo still renders a confident, wrong answer.
That shaped a principle in the code. Every layer degrades openly rather than fabricating. If search is unavailable, it says so. If the code region can't be read, it reports DRIFTED and marks it unverified rather than claiming the old fix still applies. In an incident, a confident wrong answer is worse than no answer.
What's next
Auto-capture resolutions as well as issues, so the memory grows without anyone having to remember to write it down. Multi-repo grounding is already there via path-prefix routing. Confidence scoring on the MATCH/DRIFTED verdict. Jira and Linear through the same MCP client.
Built With
- ai-agents
- block-kit
- bolt-js
- devops
- github
- github-api
- groq
- incident-response
- json-rpc
- llama
- llm
- mcp
- model-context-protocol
- murf
- node.js
- real-time-search
- render
- server-sent-events
- slack
- slack-api
- socket-mode
- typescript
Log in or sign up for Devpost to join the conversation.