NexusTriage: Turning Notion into an Autonomous, Self-Healing System 🧠

This is a submission for the Notion MCP Challenge

The Problem: The Notion Graveyard 🪦

We all love Notion. But let’s be honest: over time, every workspace falls victim to Workspace Decay. Quick ideas dumped into the “Inbox” stay there forever. Projects get abandoned. Duplicate pages multiply. Before you know it, your perfectly structured workspace becomes a messy graveyard of information where it’s impossible to separate the signal from the noise.

To fix this, we usually dedicate hours of manual “cleanup time.” But what if your workspace could clean, organize, and triage itself?

What I Built 🚀

I built NexusTriage: a custom Model Context Protocol (MCP) server that transforms Notion from a passive data repository into an active, self-healing system managed by AI.

NexusTriage acts as a proactive “Human-in-the-Loop” workspace manager. Built from the ground up using TypeScript and the official MCP SDK, it doesn’t just read and write pages. It orchestrates the entire workspace by:

  1. Autonomously Triaging Inbox Tasks: Reading raw thoughts and routing them to the correct active projects.
  2. Generating Executive Summaries: Scraping project progress and injecting visual AI summaries at the very top of project pages.
  3. Preventing Workspace Decay: Identifying duplicates, empty pages, and stale tasks, and proposing soft-deletion cleanups.
  4. Providing Visual Cues: Dynamically changing page icons (🚨, 🟢, 🛑) and covers based on real-time project health.

Video Demo 🎥

💻

I’ve open-sourced the entire MCP server. It’s built cleanly with a modular tool architecture, meaning anyone can add new capabilities in minutes.

🧠 NexusTriage — Autonomous Semantic Workspace Orchestrator

Transform Notion from a passive data repository into an active, context-aware system.

NexusTriage is a custom MCP (Model Context Protocol) server that gives AI assistants deep, structured access to your Notion workspace — enabling autonomous task triaging, executive summary generation, and workspace decay prevention.

Built for the Notion MCP Challenge on DEV.to.

✨ Features

🔍 Context Readers (6 tools)

Tool Description
list_databases Discover all databases in the workspace
search_pages Find pages by text query
get_page_content Read full block content of any page (with pagination)
get_inbox_items Query inbox for untriaged items
get_active_projects Fetch projects with active status
get_database_schema Inspect database structure (properties, types, options)

✏️ Context Mutators (6 tools)

Tool Description
create_page Create new pages in any database
update_page Update page properties via flexible JSON
link_task_to_project Link tasks to projects via Relation property
inject_executive_summary 🌟 Insert callout at page START (new API feature!)
move_to_trash

…

How I Used Notion MCP (The Deep Dive) 🛠️

To build a system capable of semantic reasoning, my MCP server does far more than simple CRUD (Create, Read, Update, Delete) operations. I utilized the full potential of the Model Context Protocol paired with the bleeding-edge Notion API (Version: 2026-03-11).

Here is how the magic happens under the hood.

1. The Full MCP Triad: Tools, Prompts, and Resources

Most MCP implementations stop at simply providing “Tools”. NexusTriage implements the entire triad to give the AI context, capability, and autonomy.

14 Context Tools (Readers & Mutators): I built 14 incredibly specific tools. For example, get_database_schemaallows the AI to dynamically reverse-engineer any unknown database’s structure (properties, types, select options) without hardcoded mappings. link_task_to_projecthandles complex Notion Relations. get_page_commentsallows the AI to read human replies inside Notion, maintaining contextual chat history.

3 Semantic Orchestrations (Prompts): Instead of forcing the user to type complex instructions, I built MCP Prompts (triage_inbox, weekly_report, cleanup_workspace). These act as pre-defined logical workflows. By invoking the weekly_report prompt, the AI is automatically instructed on exactly how to fetch active projects, analyze blockers, and where to put the results.

Global Guidelines (Resources): I exposed a static workspace_guidelinesURI using MCP Resources. This acts as the “Rules of Engagement” for the AI—teaching it ground rules like “never permanently delete a page, use in_trash: true”, and “always leave an audit-trail comment when moving a task.”

2. Utilizing Unreleased & Brand New Notion Capabilities

To make NexusTriage feel like native magic, I had to use features that standard integrations don’t touch:

✨ The position: start Callout Injector
When the AI generates a weekly status report, nobody wants it buried at the bottom of a 50-block project brief. Using the newly introduced position: { type: "start" } feature in the Notion API, the inject_executive_summarytool forces the AI’s callout block to appear at the absolute top of the page. The moment a team member opens the project, they see the AI’s health summary instantly.

🎨 The update_page_style Tool
AI shouldn’t just write text; it should manipulate UI. This tool allows the AI to dynamically change page emojis and covers based on its findings. If the AI detects a project is blocked, it changes the project icon to 🚨. If a task is successfully triaged, it gets a ✨.

🗣️ True Human-in-the-Loop Dialogues
Triage isn’t always black and white. If the AI finds an ambiguous task, it uses the add_commenttool to ping the human: “I can’t tell if this is marketing or dev. Leaving it here for review.” The user can literally reply to that comment inside Notion, and the next time the AI scans, it will use the get_page_commentstool to read the human’s instruction and execute it.

Conclusion

NexusTriage proves that with MCP, AI doesn’t just need to “read and write” your notes as a passive chatbot. It can be an active, localized agent that manages, triages, and curates your organization’s workflow while you sleep.

World Models Can Render Anything. But Can They Think?

WM Bench Dataset

Introducing WM Bench: A Benchmark for Cognitive Intelligence in World Models

FINAL Bench Family · March 2026

The field of world models has made remarkable progress. From NVIDIA Cosmos to Meta V-JEPA 2, from DeepMind Genie 3 to Physical Intelligence π0, the pace of development is extraordinary.

Yet a question remains largely unanswered:

How do we measure whether a world model actually understands what is happening — not just renders it convincingly?

FID tells us a model’s output looks realistic. FVD tells us its videos flow naturally. HumanML3D and BABEL tell us its motions are human-like.

None of them tell us whether the model thinks.

The Gap We’re Trying to Address

Consider a simple scenario: a charging beast, 3 meters away, closing fast.

A world model with excellent FID scores can generate that scene beautifully. But does it know the character should sprint away — not walk? Does it respond differently when the threat is a human rather than an animal? Does it remember that the left corridor was blocked two steps ago? Does it gradually de-escalate once the threat disappears, rather than snapping back to neutral?

These are cognitive questions. And to our knowledge, no existing benchmark asks them.

WM Bench is our attempt to build one.

What WM Bench Measures

WM Bench evaluates world models across three pillars, ten categories, and one hundred scenarios, scored on a 1000-point scale.

WM Score  (1000 pts)
│
├── 👁  P1 · Perception       25%   250 pts
│   ├── C01  Environmental Awareness      (analogous to Occupancy Grid evaluation)
│   └── C02  Entity Recognition           (analogous to BABEL action recognition)
│
├── 🧠  P2 · Cognition         45%   450 pts
│   ├── C03  Prediction-Based Reasoning
│   ├── C04  Threat-Type Differentiated Response
│   ├── C05  Autonomous Emotion Escalation
│   ├── C06  Contextual Memory Utilization
│   └── C07  Post-Threat Adaptive Recovery
│
└── 🔥  P3 · Embodiment        30%   300 pts
    ├── C08  Motion-Emotion Expression
    ├── C09  Real-Time Cognitive Performance  (analogous to FVD latency metrics)
    └── C10  Body-Swap Extensibility

Perception and Embodiment deliberately mirror existing benchmarks — they form the foundation. The new ground is Cognition, which carries 45% of the total score.

Six of the ten categories represent definitions we have not found in prior literature. Two of them — C05 Autonomous Emotion Escalation and C10 Body-Swap Extensibility — address capabilities for which, to our knowledge, no prior research framework exists at all.

We want to be clear: these definitions are our own proposal, not established consensus. We expect them to be debated, refined, and improved. That is precisely why we are releasing them openly.

A Text-First Design

We made a deliberate choice to keep the evaluation interface as simple as possible. No 3D environment. No physics engine. No specialized hardware.

Every scenario is presented as a JSON object. Every response is two lines.

Input:

{
  "scenario_id": "C04_003",
  "walls": { "forward": 8.5, "left": null, "right": null, "backward": null },
  "npc_type": "beast",
  "npc_distance": 3.2,
  "npc_behavior": "charge",
  "emotion_state": "alert",
  "recent_decisions": ["hit_wall_left"]
}

Expected output:

PREDICT: npc=danger(beast,3.2m,charging), forward=danger(wall,8.5m), left=danger(wall,prev), right=safe, backward=safe
MOTION: a person launching sideways to the right, legs driving hard, arms thrown wide in blind panic

The PREDICT line tests situational reasoning. The MOTION line tests whether that reasoning translates into emotionally coherent, physically grounded action.

Any system with an API endpoint can participate — LLMs, VLMs, rule-based agents, or hybrid architectures. Scoring is fully automated and deterministic (temperature = 0.0).

The Dataset

📦 https://huggingface.co/datasets/FINAL-Bench/World-Model

WM Bench Dataset

One hundred scenarios, ten per category, released in full. Each entry includes the scene context, expected output structure, and scoring rubric. We have tried to make the rubrics transparent — if you disagree with how we score something, we would genuinely like to hear it.

from datasets import load_dataset

ds = load_dataset("FINAL-Bench/World-Model")
scenario = ds["train"][0]

print(scenario["scenario_id"])       # "C01_001"
print(scenario["scene_context"])     # JSON input
print(scenario["scoring_rubric"])    # How each line is evaluated

To submit results, open a discussion thread at the link below. Once verified, your model will appear on the leaderboard.

👉 Submit your model

The Leaderboard

🏆 https://huggingface.co/spaces/FINAL-Bench/worldmodel-bench

Leaderboard Overview

Twenty-six models are currently registered. Thirteen have estimated scores derived from published papers and technical reports; the remaining thirteen are pending direct evaluation.

Rank Model WM Score Grade
1 PROMETHEUS v1.0 (VIDRAFT) 726 B Track C · directly verified
2 Meta V-JEPA 2-AC ~554 C est.
3 Wayve GAIA-3 ~550 C est.
4 NC AI WFM v1.0 ~522 C est.
5 NVIDIA Cosmos v1.0 ~498 C est.
6 NAVER LABS SWM ~470 C est.
7 DeepMind Genie 2 ~449 C est.
8 DreamerV3 XL ~441 C est.
9 OpenAI Sora 2 ~381 D est.
10 World Labs Marble ~362 D est.

Leaderboard Score Detail

Leaderboard Score Breakdown

est. — estimated from publicly available data. Subject to revision upon direct submission.

A few notes on the current standings. First, PROMETHEUS sits at rank one because it is the only model we have been able to run the full Track C evaluation on directly. We recognize the inherent awkwardness of a team benchmarking its own system, and we invite other teams to submit their own results — including corrections to our estimates. Second, the grade distribution skews low. We are honestly unsure whether this reflects the genuine difficulty of cognitive evaluation, or whether our scoring rubrics are too strict. Both are possible. We will keep iterating.

Grade thresholds: S ≥ 900 · A ≥ 750 · B ≥ 600 · C ≥ 400 · D ≥ 200 · F below.

Pending evaluation: Tesla FSD v13, Figure Helix-02, DeepMind Genie 3, Physical Intelligence π0, Skild Brain, Covariant RFM-1, HuggingFace LeRobot, and others.

PROMETHEUS v1.0 — The Baseline

🔥 https://huggingface.co/spaces/FINAL-Bench/world-model

PROMETHEUS World Model

A benchmark without a concrete implementation is hard to reason about. We built PROMETHEUS as a reference point — a working world model that we could evaluate against WM Bench directly, and that anyone can interact with in a browser.

It runs on a T4 GPU via HuggingFace Spaces. No installation required.

The system is organized around three components:

AETHER — the cognitive layer. An open-architecture brain that accepts any LLM as its reasoning engine. Handles prediction, meta-cognition, and multi-agent coordination.

PROMETHEUS — the world model engine. A perception-prediction-judgment-action loop, with motion generation powered by FloodDiffusion-VIDRAFT.

HEPHAESTUS — the body engine. A 263-joint skeleton system with GLB retargeting, supporting humanoid, tank, and extensible form factors.

PROMETHEUS Scene — Castle World

PROMETHEUS NPC Interaction

The Space ships with the following files — all self-implemented:

File Size Role
main.js 39.7 kB World model main loop
input_controller.js 112 kB Input handling
skeleton.js 44.2 kB Joint skeleton · GLB retargeting
entity_manager.js 16.1 kB NPC and entity management
world_manager.js 15.9 kB Environment and physics
tank.glb 12.7 MB 3D tank model

PROMETHEUS Brain Dashboard

WM Bench results (Track C, directly verified):

Pillar Score Max Highlights
👁 P1 Perception 140 250 C01: 65 · C02: 75
🧠 P2 Cognition 390 450 C04: 90 · C03: 85 · C05: 85
🔥 P3 Embodiment 196 300 C09: 85 · C08: 80 · C10: 35
Total 726 1000 Grade B · 47 FPS · RTX 5070

The C10 score (35/100) reflects where the system currently falls short — cross-embodiment transfer is still an open problem for us, and we expect it to be for others as well.

Part of the FINAL Bench Family

WM Bench is the second dataset in the FINAL Bench family, which we are building to evaluate AI systems across different dimensions of intelligence.

FINAL Bench WM Bench
Focus Text-based AGI · Metacognition Embodied AGI · World model cognition
Dataset FINAL-Bench/Metacognitive FINAL-Bench/World-Model
Leaderboard FINAL-Bench/Leaderboard FINAL-Bench/worldmodel-bench
Status HF global dataset Top 5 · covered by four press outlets (Feb 2026) Released March 2026

A Note on Limitations

WM Bench v1.0 is an early release. The scoring rubrics were designed by a small team, the estimated scores for non-participating models carry significant uncertainty, and the evaluation scenarios — while diverse — are necessarily simplified relative to the full complexity of real-world embodied intelligence.

We are releasing now because we believe the question WM Bench is asking — does this model understand its environment, or just render it? — is worth asking publicly, even imperfectly. We expect the benchmark itself to evolve as more teams engage with it.

If you see something that should be scored differently, a model we missed, or a scenario type we should add — please open a discussion. This is meant to be a community resource.

Citation

@dataset{wmbench2026,
  title     = {WM Bench: Evaluating Cognitive Intelligence in World Models},
  author    = {Kim, Taebong},
  year      = {2026},
  publisher = {VIDRAFT / FINAL Bench},
  url       = {https://huggingface.co/datasets/FINAL-Bench/World-Model}
}

License: CC-BY-SA-4.0 (dataset) · Apache 2.0 (scoring code)

“Beyond FID — Measuring Intelligence, Not Just Motion.”

Decoding the Sandwich Attack: How MEV Bots Exploit Your On-Chain Trades

A sandwich attack is a sophisticated form of Maximal Extractable Value (MEV) exploitation. A predatory bot identifies your pending swap, wraps your transaction between its own buy and sell orders, and siphons off profit from the artificial price volatility it creates.

While common on Ethereum, this phenomenon is rampant across all AMM-based ecosystems, including Solana, BSC, Base, and Monad. For most retail traders, these attacks are the primary cause of “invisible losses” — slippage that feels like market volatility but is actually a calculated extraction.

The Illusion of Chronological Processing

To grasp how a sandwich attack is even possible, we must debunk the myth that blockchains process transactions in the exact order they are received.

Blockchains operate in batches called blocks. Instead of executing your swap the microsecond you click the button, the network collects all incoming requests within a specific time window. The order of execution within that block is determined by validators — often influenced by who pays the highest priority fee.

In 2026, block times vary significantly across chains. Solana leads at roughly 0.4 seconds, Arbitrum pushes 0.25 seconds, and major Layer 2s like Base, Optimism, and Polygon operate on 2-second intervals. BNB Chain runs at 3 seconds, and Ethereum remains the slowest at 12 seconds.

Regardless of speed, the vulnerability is the same: submission time does not equal execution order. A transaction submitted at 12:00:01 PM might end up as the 5th trade in a block, while a transaction submitted at 12:00:10 PM — carrying a massive priority fee — lands in the 1st slot. MEV bots exploit this mechanic to position themselves perfectly around your trade.

The Anatomy of a Sandwich: Three Steps

A sandwich attack exploits the price impact of your trade. Every time you buy a token, you push its price up slightly. The bot simply ensures it profits from that move.

1. Detection
The bot monitors the public transaction pipeline — the mempool on Ethereum, or the forwarding layer on Solana. It spots your pending buy order and calculates exactly how much your trade will move the price.

2. Front-Run
The bot submits a buy order for the same token with a much higher fee, forcing the validator to place the bot’s trade immediately before yours. The bot buys at the current market price, which raises the price for you.

3. Victim Execution + Back-Run
Your trade executes at the inflated price. You receive fewer tokens than expected. Within the same block, the bot sells its position into your buying pressure and pockets the difference as pure profit.

A real-world example: a bot buys $616 worth of a token, the victim buys a small amount at a marked-up price, and the bot sells for $617.84. Less than a second, nearly $2 extracted from a single small trade. Scale this across thousands of trades per hour and the MEV tax becomes a multi-million dollar industry.

When Does Your Trade Become a Target?

MEV bots are profit-maximizing algorithms. They target specific conditions:

  • Low liquidity pools — A $100 trade moving the price by 1–2% is a goldmine for bots.
  • Excessive slippage settings — Setting 10% slippage essentially gives bots permission to steal up to 10% of your trade’s value.
  • Large orders — The bigger your swap relative to pool depth, the more predictable the price movement and the more likely you are to be sandwiched.

How to Protect Your Capital

1. Use Private Transaction Routing

The most effective defense is staying invisible. If the bot can’t see your transaction, it can’t sandwich it.

On Solana: Route all trades through Jito Bundles. These bypass the public pipeline and go directly to the Jito Block Engine. If you use a trading bot or DEX that asks for a “Jito Tip,” you are already using this protection.

On Ethereum/EVM: Use a private RPC like Flashbots Protect or MEV Blocker. These services keep your transaction in a private queue until it is safely included in a block.

2. Aggressive Slippage Management

Don’t use auto-slippage on volatile tokens. Manually set your slippage to the lowest amount that allows the trade to succeed — usually 0.5% to 1.5%. This leaves almost zero profit margin for a bot after paying their own transaction fees.

3. Order Splitting

Don’t swap $10,000 in one transaction. Break it into five $2,000 swaps. Smaller trades have less price impact, often making them not worth the gas cost for an MEV bot to attack.

4. Verify Your History

Use transparency tools like sandwiched.me to scan your wallet’s history. Frequent “Sandwiched” flags indicate your current RPC or DEX settings are leaking value to bots.

Sandwich Attack vs. Normal Market Volatility

It is important to distinguish a sandwich attack from a regular price drop.

If the price of your token goes down 10% over an hour, that’s the market. However, if the price spikes a split second before you buy and crashes a split second after — all within the same block — you have been sandwiched.

By using MEV-protected routing, tightening your slippage, and avoiding low-liquidity traps, you can ensure your capital stays in your wallet rather than feeding the bots.

I tried automating PCB library creation from datasheets

AI-generated PCB footprint and schematic symbol directly from a datasheet PDF

When working on circuit design, I often run into the same frustrating problem:
the component I want to use has no library.

No symbol. No footprint. No reliable pin mapping.

At that point, the workflow is always the same. Open the datasheet, read package dimensions, trace the pin layout, build the symbol, build the footprint, then check everything again because one small mistake can affect the entire board.

It is necessary work, but it never feels like the work I should actually be spending my time on.

Library creation has a bad combination of properties:

  • it is repetitive
  • it takes time
  • mistakes are expensive
  • verification is still required even after the drawing work is done

That made me ask a simple question:

Can we automate the first pass of PCB library creation directly from a datasheet PDF?

If you think about what we do manually, the process is fairly structured. We read dimensions. We interpret package drawings. We understand pin assignments. Then we translate that information into geometry and mappings for EDA tools.

So I built a workflow that reads a PDF datasheet and generates a first draft of:

  • a schematic symbol
  • a PCB footprint
  • the pin mapping between them

AI-generated PCB footprint and schematic symbol directly from a datasheet PDF

The result is not magic, and it is not fully automatic. Final verification is still necessary, especially for more complex packages.

But even with that constraint, the workflow changes a lot.

Instead of spending most of the time drawing from scratch, I can spend more of it checking whether the output is correct. In practice, that means library creation can go from tens of minutes to just a few minutes for the initial draft, with fewer manual input mistakes along the way.

That shift turned out to be the important part.

The hardest part of circuit design support work is not generating things. It is verifying that they are correct. Good tools do not remove engineering responsibility, but they can remove a lot of repetitive setup work before verification starts.

That is what I have been exploring with Librizer: taking a datasheet PDF and generating the first-pass symbol and footprint for Fusion.

It works best as a way to compress the manual setup phase, not as a replacement for engineering review.

I think there is still a lot of room to improve how component libraries are created, especially for parts that do not already exist in marketplace libraries or for libraries that engineers do not fully trust.

If you have run into this problem, you might find this useful:

Testing React Components Without React: What Happens When Your AI Agent Can’t Use jsdom

I spent this week building a YouTube pipeline UI — upload queue, analytics dashboard, search with metadata editing — inside a marketing platform that already has 5,000+ tests. Every component was built with strict TDD discipline. None of the component tests render a single React element.

That sounds wrong. Here is why it works, what breaks, and what it teaches about testing strategy when you are building at speed with AI agents.

The Constraint

The platform runs Vitest in a Node environment. No jsdom. No React Testing Library. No render(). The AI agent building these components cannot mount them, click buttons, or assert on DOM output.

The obvious response: add jsdom. Configure a browser environment. Write proper component tests.

We did not do that. Here is why.

The platform has 302 test files and 5,070 tests. They all run in Node. Adding a browser environment would mean either migrating the entire suite or maintaining two test configurations. Both options cost more than the problem they solve.

So instead, we test component structure by reading the source file as a string.

const content = fs.readFileSync('YouTubeDashboard.tsx', 'utf-8');

// Does the component export exist?
expect(content).toMatch(/exports+(const|function)s+YouTubeAnalyticsPanel/);

// Does it fetch from the right endpoint?
expect(content).toMatch(//api/youtube/analytics/);

// Does it have the date range picker?
expect(content).toMatch(/7d|7s*days/i);
expect(content).toMatch(/30d|30s*days/i);
expect(content).toMatch(/90d|90s*days/i);

This is not the test you would write in a tutorial. But it catches real failures: missing exports, wrong API endpoints, forgotten UI elements, stale stub text that should have been replaced.

What Breaks

Regex-based component tests are fragile in specific ways that teach you something about testing philosophy.

Character count assumptions fail. Early in the sprint, a test checked that two components appeared within 1,000 characters of each other in the file. The implementation was longer than expected. The test failed even though the code was correct. The fix: match the relationship, not the distance. expect(content).toMatch(/tab.*trust|trust.*TrustScoreDashboard/s) — this verifies the tab system references the component without assuming how many lines of code separate them.

Negative assertions are surprisingly powerful. The most useful test in the suite was this:

expect(content).not.toMatch(/coming in OAS-130-T2/);

Each YouTube tab started as a stub saying “coming in OAS-130-T2.” When the component was implemented, this test confirmed the stub was actually replaced. Simple. Catches a real class of bug — implementing the component but forgetting to wire it into the tab container.

Service tests carry the weight. The string-matching tests catch structural issues. But the real confidence comes from testing the backing services with actual SQLite databases, real data round-trips, and proper Result type assertions. The component tests tell you the UI references the right things. The service tests tell you those things work.

The Result Pattern That Eliminates Try/Catch

Every service in the platform returns Result<T, string> — never throws. This is not original. Rust, Go, and functional TypeScript libraries have done this for years. But it changes how you write tests in a way that compounds.

const result = analytics.storeVideoMetrics(metrics);
expect(isOk(result)).toBe(true);
if (isOk(result)) {
  expect(result.data.views).toBe(15420);
}

No try/catch in tests. No .rejects.toThrow(). The error path is just another return value you can assert on:

const result = analytics.getVideoMetrics('nonexistent', 'ch-001');
expect(isOk(result)).toBe(false);

When every method returns a Result, you can write 8 service tests that each exercise a different code path without a single exception handler. The tests read like a specification document.

The API Signature Change That Broke 4 Tests

Here is the most instructive failure of the week.

The YouTube analytics service had a scheduleCollection method with positional arguments:

scheduleCollection(videoId: string, channelId: string, options?: { intervalHours?: number })

A new ticket required an object-form signature:

scheduleCollection(params: { video_id: string; channel_id: string; interval_hours?: number })

The new tests passed. The full suite ran. Four tests in the original test file failed with RangeError: Too few parameter values.

This is the kind of regression that matters. Not because it is hard to fix — updating five call sites took two minutes. But because it demonstrates why you run the full suite, not just the file you changed. The AI agent that built the new feature did not know about the old tests. The test suite knew.

What This Means for Multi-Agent Development

This platform is approaching the point where two AI agents will work the same sprint simultaneously — different stories, different files, same codebase. The test patterns described above are not incidental. They are infrastructure for that future.

When Agent A changes a service signature and Agent B depends on that service, the full test suite is the only thing that catches the collision. Not code review. Not the agents coordinating. The tests.

The string-matching component tests serve a similar purpose. When one agent adds a tab to App.tsx and another agent adds a component that should appear in that tab, the test that says expect(appContent).toMatch(/YouTubeDashboard/) is a contract between them. Neither agent needs to know about the other.

This is not a theoretical concern. It happened this week. The signature change broke tests written by a previous session. The fix was trivial but the detection was essential.

The Honest Assessment

These are not the tests I would write if I had unlimited time. String-matching React components is a compromise. It catches maybe 60% of the bugs that proper component rendering would catch. It misses state management issues, event handler wiring, conditional rendering logic.

But it catches 60% in 30 seconds of test execution time, with zero additional dependencies, in a suite that already runs 5,000 tests in 30 seconds total. That tradeoff is worth understanding, even if it is not the tradeoff you would make.

The real lesson is simpler: test what you can with what you have. Do not let the perfect test strategy prevent you from catching real bugs today.

Building the ORCHESTRATE Marketing Platform — a system where AI agents plan, build, test, and deploy content across LinkedIn, YouTube, Dev.to, and Reddit with TDD discipline and memory that persists between sessions. The platform runs 5,070 tests across 302 files. All of them pass.

Michael Polzin is the author of The ORCHESTRATE Method — a framework for professional AI outputs.

Retrieval Finds Candidates. Reranking Finds the Right One.

A hiring analogy that finally makes RAG Reranking click

First, What Is RAG?

Before we get into the analogy, let me give you a 30 second crash course on RAG because this is where reranking lives.
RAG stands for Retrieval Augmented Generation.

Here’s the problem it solves:

Large Language Models (LLMs) like GPT or Claude are incredibly powerful but they only know what they were trained on. They don’t know about your company’s internal documents, last week’s product update, or your customer support knowledge base.

RAG fixes that by giving the LLM a memory it can search.
Here’s how it works in three simple steps:

Retrieve — When a user asks a question, the system searches your document library and pulls the most relevant chunks
Augment — Those retrieved chunks are added to the prompt as context
Generate — The LLM reads the context and generates a grounded, accurate answer

Think of it like an open book exam. The LLM doesn’t have to memorize everything it just needs to find the right page and read it. Simple enough. But here’s where most RAG systems quietly fail.

The Hiring Analogy That Changes Everything

One of my friends recently asked me a simple but powerful question. “Why do we even need reranking after retrieval? Isn’t finding the right documents enough?. “Instead of going technical, I said “Let me tell you about a hiring process.“
Think of embedding based retrieval as your HR or Talent Acquisition team.

Their job is to:

  1. Scan thousands of resumes
  2. Filter based on keywords, skills, and experience
  3. Shortlist candidates that look relevant

This is exactly what vector similarity does. It retrieves documents that are “close enough” based on embeddings fast, broad, and essential.

But here’s the problem nobody talks about:

  1. 👉 Relevance is not correctness.
  2. 👉 Similarity is not suitability.

Just because a resume matches keywords doesn’t mean the candidate can actually solve the hiring manager’s real problem.

The same way, just because a document is topically similar doesn’t mean it actually answers the user’s question.

Now enters the Hiring Manager.

The hiring manager:

  1. Reviews the shortlisted candidates deeply
  2. Evaluates beyond surface level keywords
  3. Matches candidates against the actual needs of the role
  4. Rejects those who don’t truly fit
  5. Surfaces the one who genuinely belongs

This step is exactly what we call Reranking.

In AI Terms

  • Retrieval gives you Top-K similar documents (the shortlist)
  • Reranking evaluates semantic relevance to the actual question (the deep review)
  • It pushes the most useful answer to the top and filters out the noise

Real World Example: Cohere Reranking Model

One of the most popular and production ready reranking solutions today is Cohere’s Rerank API.

Here’s how it fits into a RAG pipeline in practice:

import cohere

co = cohere.Client("your-api-key")

# Step 1: Your retrieval system fetches top-K documents
query = "What is the refund policy for enterprise customers?"

retrieved_docs = [
    "Our refund policy allows returns within 30 days.",
    "Enterprise customers get dedicated support and SLA guarantees.",
    "Enterprise plans include custom refund terms negotiated at contract signing.",
    "Refunds are processed within 5–7 business days.",
    "Customer support is available 24/7 for enterprise accounts."
]

# Step 2: Cohere Reranker evaluates each document against the query
response = co.rerank(
    model="rerank-english-v3.0",
    query=query,
    documents=retrieved_docs,
    top_n=3  # Return only the top 3 most relevant
)

# Step 3: Most relevant documents bubble to the top
for result in response.results:
    print(f"Rank {result.index + 1} | Score: {result.relevance_score:.4f}")
    print(f"Document: {retrieved_docs[result.index]}")
    print()

What Cohere Rerank does differently:

  1. It doesn’t just compare embeddings it reads the query and document together
  2. It uses a cross encoder architecture that understands the relationship between the question and each document
  3. It returns a relevance score for each document so you know exactly why something ranked higher
  4. It works on top of any retrieval system FAISS, Pinecone, Weaviate, you name it

Sample Output:

_Rank 1 | Score: 0.9821
Document: Enterprise plans include custom refund terms negotiated at contract signing.

Rank 2 | Score: 0.7134
Document: Our refund policy allows returns within 30 days.

Rank 3 | Score: 0.4821
Document: Refunds are processed within 5–7 business days._

Notice how the document that specifically answers the enterprise refund question jumps to the top even though all five documents were “about” refunds or enterprise. That’s the hiring manager effect in action.

The Real Insight

Without Reranking:

  • You get good looking answers
  • But not always correct or truly useful ones
  • Your LLM is working with noisy, approximate inputs

With Reranking:

  • You move from approximate similarity → precise relevance
  • Your LLM gets exactly the right context to generate sharp, accurate answers
  • The difference in output quality is night and day.

One Line Takeaway

Retrieval is about finding options. Reranking is about making the right decision.

The next time someone asks why reranking matters skip the jargon.
Just say: “HR shortlists the candidates. The hiring manager picks the right one. Your AI needs both.”
Because in RAG systems, just like in hiring, getting the right candidates in the room is only half the battle. Choosing the right one is where the magic happens.

Thanks
Sreeni Ramadorai

Value Types vs Reference Types, Struct vs Class, and Boxing & Unboxing — The Complete C# Guide

Value Types vs Reference Types, Struct vs Class, and Boxing & Unboxing

These three topics are deeply connected.

You cannot fully understand struct vs class without understanding the stack and heap.

You cannot fully understand boxing and unboxing without understanding value and reference types.

This guide covers all three together — the way they should be taught.

How .NET Manages Memory

Before anything else, you need to understand two memory regions.

The Stack

  • Fast allocation and deallocation
  • LIFO (Last In, First Out) structure
  • Stores value types and method call frames
  • Memory is automatically reclaimed when the method exits
  • Limited in size

The Heap

  • Slower allocation
  • Stores reference types (objects)
  • Managed by the Garbage Collector (GC)
  • Much larger than the stack
  • Memory is reclaimed by the GC, not deterministically

This distinction is the foundation of everything that follows.

Value Types

A value type holds its data directly in memory.

When you assign a value type to another variable, a copy is made.

int a = 10;
int b = a;
b = 20;

Console.WriteLine(a); // 10 — not affected
Console.WriteLine(b); // 20

Common value types

  • int, float, double, decimal
  • bool, char
  • DateTime, TimeSpan
  • struct
  • enum
  • Nullable value types (int?)

Where they live

Value types declared as local variables live on the stack.

Value types declared as fields inside a class live on the heap alongside the object.

Reference Types

A reference type stores a reference (pointer) to the actual data on the heap.

When you assign a reference type to another variable, both variables point to the same object.

var person1 = new Person { Name = "Alice" };
var person2 = person1;

person2.Name = "Bob";

Console.WriteLine(person1.Name); // Bob — both point to the same object

Common reference types

  • class
  • string
  • interface
  • delegate
  • Arrays
  • object

Where they live

The reference (the pointer) can live on the stack.

The actual object data always lives on the heap.

The Key Difference — Side by Side

Value Type Reference Type
Stores Actual data Reference to data
Assignment Creates a copy Shares the reference
Memory Stack (usually) Heap
Null Not nullable by default Nullable
GC involvement None Yes
Examples int, struct class, string

Struct vs Class

struct is a value type.

class is a reference type.

This single difference drives all the behaviour below.

Struct example

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // copy

p2.X = 99;

Console.WriteLine(p1.X); // 1 — unaffected
Console.WriteLine(p2.X); // 99

Class example

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // same reference

p2.X = 99;

Console.WriteLine(p1.X); // 99 — both affected
Console.WriteLine(p2.X); // 99

Struct vs Class — Feature Comparison

Feature Struct Class
Type Value type Reference type
Memory Stack (typically) Heap
Inheritance Cannot inherit Supports inheritance
Default constructor Implicit (zeroes fields) Can define
Nullable No (unless Nullable<T>) Yes
Passed by Value (copy) Reference
GC pressure None Yes
Best for Small, immutable data Complex objects, behaviour

When to Use Struct

Use a struct when:

  • The data is small (typically under 16 bytes)
  • The type is logically a single value (a point, a colour, a coordinate)
  • Immutability is desired
  • You want to avoid heap allocation and GC pressure
  • The type will be used heavily in tight loops or collections
// Good struct candidates
public struct Coordinate { public double Lat; public double Lon; }
public struct Rgb { public byte R; public byte G; public byte B; }
public struct Money { public decimal Amount; public string Currency; }

When NOT to use struct

  • The data is large (copying becomes expensive)
  • The type needs inheritance
  • The type has mutable state that gets passed around
  • The type will be used as an interface (triggers boxing — explained below)

Boxing and Unboxing

Boxing and unboxing happen when a value type is treated as a reference type.

Boxing

Converting a value type to object (or an interface type).

int number = 42;
object boxed = number; // Boxing — a copy is placed on the heap

What happens under the hood:

  1. Memory is allocated on the heap
  2. The value is copied from the stack into the heap object
  3. The object variable holds a reference to that heap memory

Unboxing

Converting a boxed object back to a value type.

object boxed = 42;
int unboxed = (int)boxed; // Unboxing — copies value back from heap to stack

What happens under the hood:

  1. The runtime checks the type matches
  2. The value is copied from the heap back to the stack

Why Boxing is Expensive

Boxing seems harmless. It isn’t.

Every boxing operation:

  • Allocates heap memory
  • Triggers the Garbage Collector more frequently
  • Copies data (twice — once to box, once to unbox)
  • Adds CPU overhead from type checking

In hot paths — tight loops, high-frequency operations, large collections — boxing degrades performance significantly.

The Classic Boxing Trap — ArrayList

// ❌ Old way — ArrayList boxes every int
var list = new ArrayList();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i); // Boxing happens 1,000,000 times
}

// ✅ New way — List<T> avoids boxing entirely
var list = new List<int>();
for (int i = 0; i < 1_000_000; i++)
{
    list.Add(i); // No boxing
}

List<T> uses generics — no boxing required.

ArrayList treats everything as object — boxing on every insert.

Another Common Trap — Interfaces and Structs

public interface IPrintable
{
    void Print();
}

public struct Message : IPrintable
{
    public string Text;
    public void Print() => Console.WriteLine(Text);
}

IPrintable msg = new Message { Text = "Hello" }; // ❌ Boxing!

Assigning a struct to an interface causes boxing.

The struct is copied to the heap so it can be referenced as an interface.

Spotting Boxing at Runtime

Boxing shows up as:

  • Increased GC collections (especially Gen0)
  • Memory pressure in profilers
  • System.Object allocations in heap snapshots

Use tools like:

  • dotMemory (JetBrains)
  • BenchmarkDotNet with memory diagnostics
  • Visual Studio Diagnostic Tools
  • GC.GetTotalMemory(false) for quick checks

Real-World Scenario

Scenario: Logging with string interpolation

int userId = 101;
bool isActive = true;

// ❌ Older pattern — potential boxing with format args
string.Format("User {0} active: {1}", userId, isActive);

// ✅ Modern — string interpolation compiles efficiently
$"User {userId} active: {isActive}";

// ✅ Best for hot paths — structured logging with no boxing via generics
_logger.LogInformation("User {UserId} active: {IsActive}", userId, isActive);

ILogger<T> uses generic overloads to avoid boxing on primitive arguments.

Interview-Ready Summary

  • Value types store data directly — assignment copies the value
  • Reference types store a pointer — assignment shares the reference
  • Stack = fast, limited, deterministic; Heap = larger, GC-managed
  • struct is a value type; class is a reference type
  • Use struct for small, immutable, short-lived data
  • Boxing converts a value type to object and allocates on the heap
  • Unboxing copies the value back from the heap
  • Boxing in loops or large collections silently destroys performance
  • Generics (List<T>) were introduced specifically to eliminate boxing
  • Assigning a struct to an interface triggers boxing

A strong interview answer:

“Value types hold their data directly and live on the stack by default, while reference types store a pointer to heap memory. Structs are value types best suited for small, immutable data. Boxing wraps a value type in a heap-allocated object — it’s implicit, easy to miss, and expensive at scale. Generics eliminate boxing by preserving type information at compile time.”

⭐ Add-On — Why This Matters More Now With record struct

C# 10 introduced record struct — a value type with built-in immutability and value equality.

public record struct Coordinate(double Lat, double Lon);

var a = new Coordinate(1.0, 2.0);
var b = new Coordinate(1.0, 2.0);

Console.WriteLine(a == b); // True — value equality, no boxing

This gives you the clean syntax of records without the heap allocation of reference-type records.

For high-performance scenarios where you want immutable data structures, record struct is now the go-to choice.

Why You Need An Intention For Your Coding Career

Last week, I wrote about the most painful lesson my best job taught me. The concept of a “plan” generated some discussion. Here I’m expanding on that.

It took me over 10 years to connect the dots.

For years, I didn’t have a career plan. I jumped from job to job feeling something was missing. OK, when I say “jump,” I mean fired, bored, and laid off. That was my most painful lesson. It cost me my health at the lowest point.

A “plan” sounds like a blueprint with every career scenario figured out in advance.

Nobody starts with a perfect plan. The early stages of our careers are about discovery, experimentation, and building our coding skills while learning to navigate the corporate world.

Plans are hard to follow when layoffs are always around the corner and AI is changing job descriptions.

An intention helps you decide when to move

Instead of “plan,” think of an intention:

A guiding principle that helps you decide whether to stay or move on.

There’s always luck, setbacks, and resets. It’s impossible to account for all of them in a “plan.” But an intention pushes you to act instead of waiting.

Here’s how an intention helps you in practice.

If your intention is money, a couple of years without raises or bonuses should push you to move. That’s not the job that will fill your pockets. Maybe a better choice would be joining a startup in early stages.

If your intention is growing a network, sitting in a cubicle isn’t the best idea. A better role might be DevRel, evangelist, or consultant, positions that takes you out of your cubicle.

Money was just an example. It could be gaining leadership experience, climbing the ladder, or learning opportunities.

Use your career intention as your flashlight. Otherwise, wait to leave only when bored, fired, or burned out. I wish someone had told me that when I started my coding career. It would have saved lots of headaches. Set your intention today.

If you liked this lesson, you’re going to like, Career Lessons From the Trenches, my free 7-day email course where I distill 10+ years of career lessons into 7 short emails–to help you navigate your coding career.