How I Built RasmalAI: A Real-Time Incident Correlation Engine on Top of Coral
The problem with incident response isn’t detection. It’s connection.
How I Built RasmalAI: A Real-Time Incident Correlation Engine on Top of Coral
The problem with incident response isn’t detection. It’s connection.
It’s 2am. Your phone buzzes. Slack is on fire. The prod-incidents channel has seven messages in the last four minutes — authentication failures, elevated latency, someone posting “is login down for anyone else?”
You open GitHub. There’s a PR that merged three hours ago. You open the security advisory feed. There’s a CVE for a library your auth service depends on, published yesterday.
Are these three things connected? Almost certainly. But right now, in that moment, you have three browser tabs open, a Slack thread you’re half-reading, and a GitHub commit diff that’s 400 lines long. The connection exists. Finding it costs you thirty minutes you don’t have.
That’s the problem RasmalAI was built to solve.
What RasmalAI Is
RasmalAI is a real-time incident correlation and threat intelligence platform. It pulls live data from GitHub (repository activity, security advisories) and Slack (incident and security channels), then uses AI to find causal links across all three feeds simultaneously — surfacing the connection between a deploy, an incident, and an advisory before a human even knows to look.
The name comes from Rasmalai — a Bengali dessert with distinct layers that only come together into something complete when combined. That’s exactly what incident data looks like: GitHub data makes sense in GitHub, Slack messages make sense in Slack, advisories make sense in their feed. Only when you combine the layers does the picture emerge.
We built it during the Coral hackathon in about 72 hours. This is the technical story of how.
The Architecture Decision That Made Everything Else Possible
Before writing a single line of application code, we faced a choice that would define the entire project.
Option A: Build three separate integrations. GitHub REST API with OAuth. Slack Web API with token management. GitHub Advisory API with its own pagination. Three different auth flows, three different rate limit strategies, three different response schemas to normalize.
Option B: Use Coral.
Coral is a SQL interface over live SaaS APIs. You write a SELECT statement. It hits GitHub or Slack under the hood and returns rows. That’s the entire mental model. We chose Option B in about thirty seconds.
Here’s what that choice looked like in practice. Instead of this:
python
# The old way — three different integrations
github_headers = {"Authorization": f"token {GITHUB_TOKEN}"}
response = requests.get(
"https://api.github.com/repos/uk-repack/RasmalAI/issues",
headers=github_headers,
params={"state": "open", "per_page": 10}
)
issues = response.json()
slack_headers = {"Authorization": f"Bearer {SLACK_TOKEN}"}
slack_response = requests.post(
"https://slack.com/api/conversations.history",
headers=slack_headers,
json={"channel": "C0B5LPBQSDR", "limit": 12}
)
messages = slack_response.json()["messages"]
We wrote this:
python
def run_coral_query(query):
result = subprocess.run(
["coral", "sql", query],
capture_output=True,
text=True
)
if result.returncode != 0:
error_detail = result.stderr.strip() or "No error details returned."
return f"ERROR (exit code {result.returncode}):\n{error_detail}"
return result.stdout
One function. Works for every data source. The query changes; the function doesn’t.
That single abstraction freed us to spend the entire hackathon on the intelligence layer instead of the plumbing layer. It’s the most important decision we made.
The Three Query Classes
RasmalAI has three intelligence modes, each built on a different class of Coral query.
GitHub Intelligence
The simplest mode. Pull security advisories and repository activity.
sql
SELECT
ghsa_id,
summary,
severity
FROM github.advisories
LIMIT 8
This returns live CVEs from the GitHub Advisory Database — the same feed that powers Dependabot. The severity field gives us CRITICAL, HIGH, MEDIUM, LOW. We filter the Threat Feed view to show only CRITICAL and HIGH, reducing noise for engineers who just want to know what's actively dangerous.
For repository monitoring, we query issues and PRs:
sql
SELECT
id,
title,
state,
user__login,
created_at
FROM github.issues
WHERE owner = 'uk-repack'
AND repo = 'RasmalAI'
LIMIT 10
One thing that cost us significant debugging time: the WHERE owner and WHERE repo scoping is mandatory in Coral. If you omit it, the query returns zero rows with no error. It doesn't fail loudly — it just returns nothing. We learned this the hard way and it's documented in Coral Atlas (more on that later).
Another quirk: nested fields use double underscore notation. user__login not user.login. Once you know this it's obvious. Before you know it, it looks like a typo.
Slack Intelligence
The Slack connector is where things get interesting. Coral treats Slack channels as parameterized table functions:
sql
SELECT
ts,
user_id,
text
FROM slack.messages(
channel => 'C0B5LPBQSDR'
)
ORDER BY ts DESC
LIMIT 12
The channel => syntax passes the channel ID as a named parameter to the table function. We maintain a dictionary of channel IDs in the app config:
python
slack_channels = {
"prod-incidents": "C0B5LPBQSDR",
"security-alerts": "C0B5WNUL6A0",
"backend-team": "C0B5V016BT3"
}
One important production consideration: if a channel gets archived or the workspace changes, the query fails. We added explicit error surfacing so the failure is visible rather than silent:
python
if query_data.startswith("ERROR"):
st.warning(
f"Slack channel '{selected_channel}' (ID: `{channel_id}`) "
"could not be queried. The channel may have been archived "
"or the ID has changed."
)
Incident Correlation — The Core Engine
This is where the architecture gets genuinely interesting.
The insight behind the correlation engine is that incident data is inherently multi-source. A deploy happens in GitHub. The incident surfaces in Slack. The vulnerability exists in an advisory feed. No single source tells the complete story. You need all three simultaneously, and you need something that can reason across them.
Here’s the full data fetch:
python
advisory_results = run_coral_query(advisory_query)
repo_results = run_coral_query(repo_query)
slack_incidents = run_coral_query(slack_incidents_query)
slack_security = run_coral_query(slack_security_query)
slack_results = f"""
PROD-INCIDENTS CHANNEL:
{slack_incidents}
SECURITY-ALERTS CHANNEL:
{slack_security}
"""
Four Coral queries. Four sources. All returned as plain text rows in a consistent format. That consistency is the key — because Coral normalizes the output format, we can concatenate GitHub data and Slack data into a single string and send it to an LLM without any source-specific preprocessing.
The Deploy Regression Detector
The correlation engine is the most technically interesting piece of RasmalAI. Here’s the design philosophy behind it.
Most “AI correlation” tools take data, dump it into a prompt, and ask the LLM to “summarize” or “find issues.” That’s not correlation — that’s summarization with extra steps. The output looks authoritative but isn’t falsifiable. An on-call engineer can’t verify it, can’t trust it, and eventually stops looking at it.
We designed the Deploy Regression Detector differently. The prompt gives the LLM three specific, answerable questions:
- Did any merged PR in the repository activity precede any incident in the Slack feed? Look for matching service names, timing proximity, error keywords.
- Do any active advisories reference libraries or services mentioned in the GitHub or Slack feeds? If yes, name the advisory and the system.
- Are there repeated patterns in Slack suggesting systemic failure rather than a one-off?
And it demands structured, parseable output:
FINDING_1:
TYPE: (deploy-regression | advisory-match | slack-pattern | none)
CONFIDENCE: (low | medium | high)
CONFIDENCE_REASON: (one sentence — what evidence supports this)
TIMELINE: (what happened first, then what followed)
AFFECTED_SYSTEM: (service, repo, or component name)
BLAST_RADIUS: (what else could be affected)
RECOMMENDED_ACTION: (one concrete step the on-call engineer should take right now)
The structured output is critical. We parse it with regex back into Python dicts:
python
def parse_correlation_findings(raw_text):
raw_text = raw_text.replace("**", "").replace("*", "").replace("#", "").replace("`", "")
finding_blocks = re.findall(
r"FINDING_\d+\s*[:\-]?\s*(.*?)(?=FINDING_\d+|OVERALL_VERDICT|$)",
raw_text,
re.DOTALL | re.IGNORECASE
)
findings = []
for block in finding_blocks:
finding_type = extract_field_from_block(block, "TYPE")
if not finding_type or finding_type.lower() == "none":
continue
findings.append({
"type": finding_type,
"confidence": extract_field_from_block(block, "CONFIDENCE"),
"confidence_reason": extract_field_from_block(block, "CONFIDENCE_REASON"),
"timeline": extract_field_from_block(block, "TIMELINE"),
"affected_system": extract_field_from_block(block, "AFFECTED_SYSTEM"),
"blast_radius": extract_field_from_block(block, "BLAST_RADIUS"),
"recommended_action": extract_field_from_block(block, "RECOMMENDED_ACTION"),
})
return findings, overall_verdict
Each finding renders as a structured card in the UI — color-coded by confidence, with the affected system, timeline, blast radius, and a single concrete recommended action. Not a paragraph of AI text. A card an engineer can read in five seconds and act on.
Why does this matter? Because trust in AI tooling is built through transparency. When a finding says “CONFIDENCE: high — the auth-service PR merged at 14:32 and the first Slack incident message appeared at 14:47,” an engineer can verify that claim independently. The LLM’s reasoning is visible, not hidden behind a vague “our AI detected an anomaly.”
The Inference Layer
We used Groq with LLaMA 3.3 70B for all AI calls. The choice was deliberate.
Groq’s inference speed is genuinely different from other providers. The correlation prompt sends 1,500+ tokens of multi-source data and asks for structured output — on most providers that’s a 5–8 second wait. On Groq it’s under a second. For a tool that’s supposed to help during an active incident, that latency difference is the difference between something you reach for and something you tolerate.
We have two AI calls in RasmalAI:
The correlation engine call — the main prompt described above, max_tokens 1500, structured output enforced through prompt design rather than function calling.
The executive summary call — a lighter prompt that takes the same data and produces a human-readable THREAT_LEVEL, PRIMARY_CONCERN, IMPACT, and IMMEDIATE_ACTIONS block for non-technical stakeholders. Both calls are wrapped in try/except with st.spinner for user feedback:
python
with st.spinner("Correlating GitHub deploys, Slack incidents, and security advisories..."):
try:
raw_correlation = run_deploy_regression_correlation(
repo_results,
slack_results,
advisory_results
)
except Exception as e:
st.error(f"Correlation Engine Error: {e}")
st.stop()
One non-obvious decision: the executive summary strips all markdown from the LLM output before rendering it in custom HTML cards. LLMs reliably ignore “no markdown” instructions for headers and bold text. So we strip it explicitly:
python
summary = (summary
.replace("**", "").replace("*", "")
.replace("##", "").replace("#", "")
.replace("__", "").replace("`", "")
)
Small thing. Makes the output look professional instead of like a raw LLM dump.
The UI Layer
RasmalAI is built on Streamlit. The aesthetic is deliberately dark and dense — a command center, not a dashboard. The design philosophy was: every pixel should communicate operational status, not decorate.
One interesting technical decision was the live clock. We wanted a ticking clock in the header to reinforce the “live intelligence session” feel. The naive Streamlit approach would be st_autorefresh with a 1-second interval — which triggers a full page rerun every second, burning Streamlit Community Cloud credits at 86,400 reruns per day.
Instead we used a pure JavaScript clock rendered through streamlit.components.v1.html:
python
components.html("""
<div id="live-clock" style="color:#E6C07B;font-size:38px;font-weight:800;">
--:--:--
</div>
<script>
function updateClock() {
const now = new Date();
const h = String(now.getHours()).padStart(2, '0');
const m = String(now.getMinutes()).padStart(2, '0');
const s = String(now.getSeconds()).padStart(2, '0');
document.getElementById('live-clock').textContent = h + ':' + m + ':' + s;
}
updateClock();
setInterval(updateClock, 1000);
</script>
""", height=140)
This runs entirely in the browser iframe. The Streamlit server is uninvolved after initial page load. Zero reruns. Zero credits. The clock ticks because JavaScript ticks — not because Streamlit rerenders.
The Risk Engine
Alongside the AI correlation, RasmalAI runs a lightweight deterministic risk scoring engine. It counts incident-related keywords across all query data and produces a risk score:
python
def calculate_risk_score(incident_mentions, critical_count, high_count):
score = (
incident_mentions * 15
+ critical_count * 25
+ high_count * 12
)
return min(score, 100)
This feeds into four dashboard metrics displayed at the top of the page: Risk Index (NOMINAL / ELEVATED / HIGH / CRITICAL), Correlation Confidence percentage, Active Signals count, and System Status.
We’re honest about what these numbers are: keyword-based heuristics, not actuarial risk scores. They’re useful for glanceability — a quick visual indicator of whether the current feed looks hot or cold — but the real intelligence lives in the correlation findings.
What Coral Made Possible (and What Comes Next)
Looking back at the architecture, the dependency on Coral is more fundamental than it might appear from the outside.
Without Coral, the correlation engine requires three separate OAuth integrations, three different response schemas to normalize, and three different pagination and rate limit strategies to manage. That’s a week of infrastructure work before you write a single line of intelligence logic.
With Coral, it’s four SQL queries and one Python function. The entire data layer is twelve lines of code. Every hour we didn’t spend on API plumbing, we spent on prompt engineering, parsing logic, UI design, and the actual intelligence problems that make RasmalAI useful.
That said, there’s a fundamental limitation in the current architecture that we’re acutely aware of. Cross-source correlation still relies on an LLM to stitch together data that Coral returns as separate result sets. The LLM is doing the join work that ideally should happen at the data layer.
The feature we’re most excited about for Coral’s future is time-window JOINs across sources. Instead of three fetches and an LLM prompt, one query:
sql
SELECT p.title, p.merged_at, s.text, s.ts
FROM github.pulls p
JOIN slack.messages s
ON s.ts BETWEEN p.merged_at
AND p.merged_at + INTERVAL '2 hours'
AND s.channel = 'prod-incidents'
WHERE p.repo = 'RasmalAI'
That single query replaces the entire fetch-stitch-correlate pipeline. The LLM becomes optional enrichment rather than the correlation mechanism itself.
After that: semantic JOINs. The hardest real-world correlation problem is that “auth-service throwing 500s” in Slack and “authentication failure on login endpoint” in a GitHub issue are the same incident but share no exact string. Fuzzy matching at the data layer — not the LLM layer — is where Coral could become genuinely irreplaceable for incident intelligence.
Coral Atlas: The Side Project That Came Out of the Debugging
While building RasmalAI we accumulated a lot of hard-won knowledge about Coral’s quirks — the owner/repo scoping requirement, the double-underscore nested field syntax, the channel parameter format for Slack queries.
Rather than let that knowledge die in a Discord message, we built Coral Atlas: an unofficial developer guide for working with Coral connectors, with working SQL examples, troubleshooting notes, and connector-specific semantics. It’s live at coral-atlas-query-ex-26t5.bolt.host.
If you’re building on Coral and you’ve hit something that isn’t in the docs, that’s where we want it documented.
link: coral atlas
The Bigger Picture
RasmalAI exists because incident response is still mostly manual, and the tools that automate it — Datadog, Splunk, PagerDuty — require weeks of instrumentation, significant budget, and dedicated platform engineers to maintain.
We wanted to build something that worked today, with the tools a typical engineering team already has, requiring no agents, no instrumentation, no new infrastructure. If your team uses GitHub and Slack, RasmalAI works.
The architecture that made that possible — Coral as the data layer, Groq as the inference layer, Streamlit as the UI layer — is a template for a class of tools that didn’t really exist before: real-time intelligence systems built on top of the SaaS data your team already generates, requiring no new data pipelines to build and maintain.
That’s what we’re most proud of. Not the UI, not the prompt engineering, not the parsing logic — but the fact that the entire intelligence stack runs on infrastructure that already exists in every engineering team’s toolchain.
The data was always there. We just needed a way to ask it the right questions.
RasmalAI was built during the Coral hackathon. The codebase, Coral Atlas, and further technical documentation are available on GitHub. If you’re building something similar or have questions about the architecture, find us in the Coral Discord. links: github | discord | live demo
메타데이터
- post_id
- d6551cffa8e8
- slug
- how-i-built-rasmalai-a-real-time-incident-correlation-engine-on-top-of-coral-d6551cffa8e8
- url
- https://medium.com/@uk.ranjan101/how-i-built-rasmalai-a-real-time-incident-correlation-engine-on-top-of-coral-d6551cffa8e8
- canonical_url
- https://medium.com/@uk.ranjan101/how-i-built-rasmalai-a-real-time-incident-correlation-engine-on-top-of-coral-d6551cffa8e8
- author_url
- https://medium.com/@uk.ranjan101
- status
- ok
- fetched_at
- 2026-07-14 19:59:56