Slag: Project Story

Inspiration

Every engineering team has a graveyard. Functions nobody calls. Classes nobody imports. Utilities that outlived their purpose by three product pivots. The dead code is there, visible and obvious, cluttering every file you open.

The tools exist too. Run a linter, get a list. A hundred "unused" functions, neatly enumerated.

And then nothing happens. The list gets ignored. It sits in a ticket, assigned to nobody, closed eventually as "won't fix."

Why? Fear.

Not laziness. Fear. What if validate_token_v1() is actually called somewhere via a string eval in a YAML config I've never seen? What if repo B imports this module? What if someone added this three weeks ago as rollback code and just forgot to remove the comment?

Static analysis tells you what's unused. Nothing tells you whether deletion is safe. That gap, between "unused" and "safe to delete," is where dead code goes to live forever.

That's what Slag solves.


What We Built

Slag is an AI agent that scans a GitLab repository for dead code and computes a Deletion Blast Radius Score for every candidate it finds. This score is a structured risk assessment that tells you exactly how dangerous a removal would be.

The agent runs autonomously in three phases:

  1. Scan — Gemini reads every file and identifies unused functions, classes, and methods
  2. Score — 5 independent risk signals are computed per candidate
  3. Ship — A GitLab MR is opened: low-risk deletions staged, high-risk candidates flagged with inline comments

The overall risk for each candidate is calculated by taking the highest score across all five signals:

$$\text{overall_risk} = \max\left(\text{signal}_1, \text{signal}_2, \ldots, \text{signal}_5\right)$$

Each signal can be low, medium, high, or unknown. If even one signal comes back HIGH, the whole candidate is flagged HIGH. This conservative approach ensures engineers can actually trust the output.

The 5 Signals

# Signal Question
1 Dynamic Call Is the function name in a string, eval(), YAML config, or env file?
2 Cross-Repo Does any other repo in the GitLab group import this?
3 Proximity Is this code near auth, payments, security, or data migration logic?
4 Age Was it recently modified, or referenced in an open MR?
5 Test Only Is it called from tests but not production?

Signal 2 (cross-repo exposure) is the one no static analysis tool can compute on its own. It requires a group-wide search across your entire GitLab organization, made possible by the GitLab API.


How We Built It

The stack is intentionally lean:

Agent Brain: Gemini 3.1 Pro Preview via Google Cloud ADK. The agent is a single Agent object with 13 registered tools. Phases 1 and 3 are free-form, meaning Gemini decides which tools to call and in what order. Phase 2 is guided, where all 5 signal tools are called explicitly per candidate to guarantee consistent, auditable reports.

File Analysis: Gemini 3.1 Flash Lite reads each file and returns structured JSON identifying dead candidates. The approach is language-agnostic by design, with no AST parsers to maintain and no language-specific rules to write. Python, JavaScript, TypeScript, Ruby, Go, Java, and C# all work through the same prompt.

GitLab Integration: Direct REST API calls via httpx. We originally tried @gitlab/gitlab-mcp (the npm package) but it returned 404. The REST API handles everything we need: listing files, reading contents, searching code group-wide, creating branches, opening MRs, and posting inline comments.

Backend: FastAPI with async SQLAlchemy and PostgreSQL. Real-time updates stream to the frontend via WebSocket, covering every file analyzed, every candidate scored, and the final MR URL, all live.

Frontend: Next.js and Tailwind with Rajdhani as the display font. The results page shows a live agent log as the scan runs, then a full blast radius dashboard with expandable candidate cards.

Deploy: Google Cloud Run via Cloud Build, with secrets stored in Secret Manager. Two services: slagai-backend and slagai-frontend.


Challenges

The model doesn't always return valid JSON. The dead code detection prompt asks Gemini to return raw JSON with no markdown fences and no preamble. Gemini frequently wraps it in backticks anyway, or adds a conversational sentence before the JSON. We added a regex-based JSON extractor that strips the wrapper and retries on parse failure. After three retries with exponential backoff, the file is marked as errored and skipped.

Cross-repo search is slow. Searching the entire GitLab group for a function name requires one API call per candidate. For a repo with 50 dead candidates, that's 50 sequential HTTP requests. We parallelized the signal checks across candidates, but the GitLab API rate limits at around 600 requests per minute. We added a semaphore to cap concurrent requests and a short sleep between batches.

"Is this dead?" is harder than it looks. Gemini tends to be conservative, flagging things as "possibly used" if there's any ambiguity. We tuned the confidence threshold to 0.7 (70%) and added explicit instructions to the prompt to distinguish between "not called anywhere I can see" and "exported, decorated, or could be an entry point." The false positive rate dropped significantly after adding cross-reference context to each analysis call.

The MR creation race condition. The agent creates a branch, pushes a report file, then opens the MR. If the branch creation succeeds but the file push fails due to a network timeout, the MR creation step finds an empty branch and GitLab rejects it. We wrapped the entire Phase 3 in a transaction-style try/except: if MR creation fails, we delete the branch and surface the error cleanly rather than leaving orphaned branches in the repo.

Keeping the frontend in sync during long scans. A scan of a 60-file repo takes 4 to 8 minutes. WebSocket connections drop. We implemented a 3-retry reconnect strategy with exponential backoff on the frontend, along with a scan:pending message type so a reconnecting client can recover the current state from the REST API rather than waiting for the next WebSocket event.


What We Learned

The most surprising insight: Gemini is better at reading code than at writing JSON. The model's code comprehension is genuinely impressive. It catches subtle dead code patterns that rule-based tools miss entirely, such as functions only called in commented-out code, or methods only reachable through a deleted code path. But structured output requires persistent prompting and discipline. The system prompt, the per-call instruction, and the retry logic all have to agree on the format.

We also learned that the hardest part of building an AI agent isn't the AI; it's the plumbing. The ADK handles the reasoning loop elegantly. What takes time is retry logic, error surfacing, partial failure handling, making sure the database reflects what the agent actually did, and making the live UI feel responsive when the underlying operation takes minutes.

Finally: fear is a product problem, not a technical one. The blast radius score exists not because it's computationally complex (the underlying checks are straightforward) but because it addresses the human reason dead code accumulates. Engineers don't delete dead code because they're afraid of being wrong. Make the risk legible, explainable, and auditable, and the fear dissolves.


What's Next

Slag works today as a hackathon demo, but there's a clear path to making it a tool any engineering team can actually use.

GitLab Authorization is the most immediate priority. Right now, users have to manually supply a GitLab access token to run a scan. The next step is a proper OAuth flow so anyone can connect their GitLab account in one click, without touching a config file. This is the single biggest unlock for making Slag accessible to people outside the team that built it.

Improved UI/UX is close behind. The current results dashboard gets the job done, but the experience of reviewing 50 candidates with expandable cards can get overwhelming quickly. We want to introduce smarter filtering, a cleaner summary view, and better visual hierarchy so engineers can triage candidates at a glance rather than reading through every card.

Team and Org Features are the longer-term vision. Dead code is a team problem, not a solo one. We want to add the ability to assign candidates to teammates, track deletion progress across sprints, and surface trends over time, like which parts of the codebase accumulate dead code fastest. For larger organizations, a dashboard across multiple repos would make Slag genuinely useful at the platform or infrastructure level.

The core insight that drove Slag, that fear (not laziness) is why dead code accumulates, applies just as much at the team level as it does for individual engineers. The goal is to make deletion feel safe, visible, and routine.


Built for the Google Cloud Rapid Agent Hackathon · GitLab Track · Powered by Gemini and Google Cloud ADK

Built With

Share this project:

Updates