By Randal L. Schwartz, and a few million TPU cycles
Motto: "With the rigor of Bloc and the flex and speed of Signal"
If you have spent any time in the Flutter community over the past eight years, you have witnessed the great "State Management Wars."
On one track sat Classic BLoC: strict, battle-tested, enterprise-grade, but heavily reliant on asynchronous Dart Stream microtasks. On an adjacent track sat Riverpod: offering compile-time safety and declarative dependency graph plumbing, but steering increasingly toward mandatory code generation and build_runner iteration tax. On the newest high-speed track arrived Signals: offering raw sub-microsecond synchronous reactivity and fine-grained UI rebuilding.
For years, choosing a state management library felt like choosing an isolated railroad network. If an engineering team built their core application with flutter_bloc or flutter_riverpod and wanted to take advantage of synchronous Signals for a new high-frequency feature, conventional wisdom dictated a painful choice: either undertake a risky, multi-month rewrite or suffer through clunky, second-class adapter boilerplate.
Traditional "interop" packages in our ecosystem have almost always been an afterthoughtâawkward, leaky wrappers designed to tolerate legacy code until someone finds the budget to delete it.
Today, with the release of bloc_signals_bloc and a major update to bloc_signals_riverpod, we are fundamentally changing that paradigm.
BLoC, Riverpod, and BlocSignal are no longer competing silos. They are first-class, bidirectional peers.
Imagine walking into a majestic railway terminalâvaulted glass arches overhead, golden sunbeams cutting through the air, and railway block signal gantries glowing bright green.
Pulling up to the platforms side by side on three parallel steel tracks are three distinct locomotives:
đ Track 1: Classic BLoC (The Steam Locomotive) ââââââ
â
đ Track 2: Riverpod (The Heavy Freight Hauler) âââââââŒâââș [ Grand Central State Terminal ] ââââș Synchronous Signals
â
đ Track 3: BlocSignal (The High-Speed Maglev) ââââââââ
In Grand Central Terminal, the tracks do not collide, and no train is treated as second-class rolling stock. Platforms sit adjacent to each other. Passengers (state, events, actions) walk across the concourse between trains with zero baggage check fees, zero customs delays, and zero microtask penalties.
In most architectures, adapting one state container to another requires wrapping everything in custom StreamController instances, registering manual listener callbacks, and remembering to clean up disposers to prevent memory leaks.
Under BlocSignal, peer integration is completely bidirectional, lifecycle-managed, and type-safe:
| From Target â To Target | How It Works | Developer Ergonomics |
|---|---|---|
| Classic BLoC â BlocSignal | classicBloc.toBlocSignal() |
Exposes synchronous .state signal + forwards .add(event)
|
| Classic Cubit â CubitSignal | classicCubit.toBlocSignal() |
Exposes synchronous .state signal + typed .cubit methods |
| Riverpod Provider â BlocSignal | provider.toBlocSignal(ref) |
Exposes synchronous .state signal + typed .notifier methods + auto-disposal |
| BlocSignal â Classic BLoC | blocSignal.toClassicBloc() |
Direct drop-in for legacy flutter_bloc BlocBuilder / BlocListener
|
| CubitSignal â Classic Cubit | cubitSignal.toClassicCubit() |
Direct drop-in for legacy flutter_bloc widgets |
| BlocSignal / CubitSignal â Riverpod | blocSignal.toProvider() |
Direct drop-in for Riverpod ref.watch and ref.read
|
Riverpod AsyncValue â Signals AsyncState |
.toAsyncState() / .toAsyncValue()
|
Seamless mapping across sealed loading/error/data states |
Let's put this into practice with a concrete example.
What does it look like when all three state engines work together in a single Flutter screen?
Here is a complete, runnable Flutter app where a Classic BLoC, a Riverpod Notifier, and a Modern CubitSignal live side by side. Each manages its own domain state, yet they compose synchronously into a unified Grand Total using a single computed signal in under 65 lines of code:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:bloc/bloc.dart' as bloc_lib;
import 'package:bloc_signals/bloc_signals.dart';
import 'package:bloc_signals_bloc/bloc_signals_bloc.dart';
import 'package:bloc_signals_riverpod/bloc_signals_riverpod.dart';
import 'package:signals_flutter/signals_flutter.dart';
// đ 1. CLASSIC BLOC: The Steam Engine (Explicit Event -> State)
class ClassicCounterBloc extends bloc_lib.Bloc<int, int> {
ClassicCounterBloc() : super(0) {
on<int>((event, emit) => emit(state + event));
}
}
// đ 2. RIVERPOD: The Freight Hauler (Declarative Notifier)
class RiverpodCounter extends Notifier<int> {
@override
int build() => 0;
void increment() => state++;
}
final riverpodCountProvider =
NotifierProvider<RiverpodCounter, int>(RiverpodCounter.new);
// đ 3. BLOCSIGNAL: The High-Speed Maglev (Synchronous Signals)
class ModernSignalCubit extends CubitSignal<int> {
ModernSignalCubit() : super(initialState: 0);
void increment() => emit(stateValue + 1);
}
// đïž GRAND CENTRAL TERMINAL: The Peer Counter Screen
class GrandCentralCounterScreen extends ConsumerWidget {
const GrandCentralCounterScreen({
super.key,
required this.classicBloc,
required this.signalCubit,
});
final ClassicCounterBloc classicBloc;
final ModernSignalCubit signalCubit;
@override
Widget build(BuildContext context, WidgetRef ref) {
// đ Adapt BLoC and Riverpod into first-class signal peers:
final blocPeer = classicBloc.toBlocSignal();
final riverpodPeer = riverpodCountProvider.toBlocSignal(ref);
// ⥠Synchronously compute the Grand Total across all three rail lines:
final grandTotal = computed(
() => blocPeer.state() + riverpodPeer.state() + signalCubit.state(),
);
return Scaffold(
appBar: AppBar(title: const Text('Grand Central State Terminal')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('đ Classic BLoC Count: ${blocPeer.stateValue}'),
Text('đ Riverpod Count: ${riverpodPeer.stateValue}'),
Text('đ BlocSignal Count: ${signalCubit.stateValue}'),
const Divider(height: 32, indent: 64, endIndent: 64),
// Reactively updates the instant ANY train leaves its station!
Watch((context) => Text(
'đ Grand Total: ${grandTotal()}',
style: Theme.of(context).textTheme.headlineMedium,
)),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton.extended(
heroTag: 'bloc',
label: const Text('+1 BLoC'),
onPressed: () => blocPeer.add(1),
),
const SizedBox(width: 8),
FloatingActionButton.extended(
heroTag: 'riverpod',
label: const Text('+1 Riverpod'),
onPressed: () => riverpodPeer.notifier.increment(),
),
const SizedBox(width: 8),
FloatingActionButton.extended(
heroTag: 'signal',
label: const Text('+1 Signal'),
onPressed: () => signalCubit.increment(),
),
],
),
);
}
}
void main() {
// Initialize classic BLoC and modern CubitSignal instances:
final classicBloc = ClassicCounterBloc();
final signalCubit = ModernSignalCubit();
runApp(
// Wrap with Riverpod's ProviderScope at the application root:
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: GrandCentralCounterScreen(
classicBloc: classicBloc,
signalCubit: signalCubit,
),
),
),
);
}
blocPeer.add(1) dispatches directly into the underlying classic Bloc event queue.riverpodPeer.notifier.increment() calls the underlying RiverpodCounter methods with full type safety.signalCubit.increment() triggers immediate synchronous signal emission.counterProvider.toBlocSignal(ref) automatically registers ref.onDispose to close the underlying bridge when the widget or provider scope unmounts. No memory leaks.grandTotal: computed(() => blocPeer.state() + riverpodPeer.state() + signalCubit.state()). package:bloc, package:riverpod, and package:bloc_signals simultaneously. When any of the three states update, grandTotal recalculates in the exact same frame with zero microtask queue hops.Peer status is not a one-way street. What if you build a cutting-edge feature using modern BlocSignal containers, but you need to embed it inside an existing application that relies entirely on flutter_bloc's BlocBuilder or Riverpod's ConsumerWidget?
You don't need to rewrite your containers!
flutter_bloc Trees
final modernCubit = ModernSignalCubit();
// Adapt to a classic flutter_bloc Cubit:
final classicCubit = modernCubit.toClassicCubit();
// Consume directly inside existing flutter_bloc widgets with standard types:
BlocBuilder<bloc_lib.Cubit<int>, int>(
bloc: classicCubit,
builder: (context, state) => Text('Legacy BLoC UI: $state'),
);
ProviderScope Trees
final modernCubit = ModernSignalCubit();
// Expose modern CubitSignal as a standard Riverpod NotifierProvider:
final myRiverpodProvider = modernCubit.toProvider();
// In any Riverpod ConsumerWidget:
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(myRiverpodProvider);
return ElevatedButton(
onPressed: () => ref.read(myRiverpodProvider.notifier).cubit.increment(),
child: Text('Riverpod UI: $count'),
);
}
This architectural milestone eliminates the single largest point of friction in Flutter development:
BlocSignal into a massive legacy Riverpod or BLoC production codebase one screen, one dialog, or one widget at a time.BlocSignal containers with sub-microsecond rendering speed.State management in Flutter does not have to be an ideological battleground.
Whether your architecture runs on the Classic BLoC Iron Horse, the Riverpod Freight Hauler, or the BlocSignal Bullet Train, Grand Central Terminal ensures green lights across all lines.
To get started today, add the peer packages to your pubspec.yaml:
dependencies:
bloc_signals: ^1.0.0
bloc_signals_bloc: ^1.0.0 # For Classic BLoC peer bridges
bloc_signals_riverpod: ^1.2.0 # For Riverpod peer bridges
bloc_signals_flutter: ^1.0.0 # For Flutter widget bindings
Check out the complete documentation, interactive API catalogs, and live showcase apps at blocsignal.dev.
See you on the tracks!
The developer tooling ecosystem has entered an era of deep agentic assistance and extreme hardware efficiency. In enterprise production environments today, software engineering is anchored on the robust LTS foundation of C♯ 14 running on .NET 10, while preview builds of C♯ 15 and .NET 11 continue to test the next frontiers of runtime execution.
The release of Visual Studio 2026 represents one of the most substantial architectural evolutions of Microsoft's flagship Integrated Development Environment. In this comprehensive technical walkthrough, we unpack the major capabilities of Visual Studio 2026, from autonomous multi-file GitHub Copilot refactoring and native zero-overhead memory profiling to accelerated parallel build pipelines.

In previous IDE versions, AI coding assistants operated primarily within the confines of single-file cursor context or standalone chat sidebars. Developers still bore the cognitive burden of manually copying code snippets across dependent interfaces, implementation classes, unit test suites, and database configuration files.
Visual Studio 2026 fundamentally re-engineers this experience by integrating GitHub Copilot Agent Mode directly into the solution workspace. Operating across the entire Roslyn syntax tree, Copilot can now plan, coordinate, and execute atomic multi-file code refactorings in a single cohesive transaction.
When you prompt Copilot to add an enterprise domain entity (e.g., "Add an AuditLogging pipeline to all controller actions"), the agent analyzes your solution architecture, updates repository contracts, writes Entity Framework Core migration snapshots, registers dependency injection services in `Program.cs`, and drafts corresponding xUnit test fixtures simultaneously.
As we previously explored in our guide on how to run local AI models in 2026, Visual Studio 2026 also allows developers to connect local ONNX and Ollama inference endpoints directly into the IDE, ensuring complete source privacy for regulated enterprise codebases.
Visual Studio 2026 presents these changes in a unified multi-diff review window, allowing you to accept, reject, or fine-tune modifications across individual files before committing them to Git.
Diagnosing memory leaks, high Garbage Collection (GC) pressure, and unintended object retention in production enterprise APIs has traditionally required attaching heavyweight external diagnostic profilers that degrade application performance.
Visual Studio 2026 introduces a completely rewritten Diagnostics Hub that leverages native EventPipe tracing and .NET 10 hardware-assisted telemetry to provide continuous, zero-overhead memory and CPU profiling directly in the debugging window.
The memory profiler now displays real-time allocation rates per second categorized by generation (Gen 0, Gen 1, Gen 2, and Large Object Heap). It automatically flags hot paths where boxing, string allocations, or temporary LINQ iterators are thrashing the Garbage Collector.
Consider the following before-and-after refactoring scenario identified instantly by the Visual Studio 2026 Allocation Tracker:
// â BEFORE: Diagnostic Hub flags 1.4 MB/sec heap allocations during HTTP request bursts
public class InvoiceSummaryService
{
public string GenerateSummary(List<InvoiceItem> items)
{
// Unnecessary LINQ and string concatenation creates millions of Gen 0 objects
return string.Join(", ", items.Select(i => i.Sku + ":" + i.Amount.ToString("C")));
}
}
// â
AFTER: Zero-allocation span-based formatting with ValueStringBuilder (0 B Heap Allocations)
public class HighPerformanceInvoiceService
{
public void FormatSummary(ReadOnlySpan<InvoiceItem> items, Span<char> destination, out int charsWritten)
{
var formatter = new ValueStringFormatter(destination);
foreach (ref readonly var item in items)
{
formatter.Append(item.Sku);
formatter.Append(':');
formatter.AppendSpanFormattable(item.Amount, "C");
formatter.Append(", ");
}
charsWritten = formatter.Length;
}
}
Reflecting on the evolution of C♯ language features over the years, having native profiling tools that seamlessly illuminate modern low-level optimizations makes writing high-throughput code vastly more intuitive.
With the stable release of C♯ 14 in .NET 10, Visual Studio 2026 delivers comprehensive IntelliSense, quick-fix refactorings, and visual diagnostic squiggles for all new language constructs.
For decades, developers were forced to declare explicit private backing fields whenever custom validation or notification logic was needed in a property accessor. C♯ 14 introduces the contextual `field` keyword, and Visual Studio 2026 provides automated one-click refactorings (`Ctrl + .`) to convert boilerplate code into concise syntax:
// Clean C# 14 Field-Backed Property in Visual Studio 2026
public class UserAccount
{
// Auto-property with accessor-level logic using the 'field' keyword
public string EmailAddress
{
get => field;
set => field = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("Email cannot be empty.")
: value.Trim().ToLowerInvariant();
} = "guest@example.com";
// Value clamping without explicit private backing field
public int RetryCount
{
get => field;
set => field = Math.Clamp(value, 0, 10);
} = 3;
}
Debugging compile-time C♯ Source Generators (such as JSON serializers, regular expression generators, and telemetry loggers) used to be notoriously opaque. Visual Studio 2026 introduces a dedicated Source Generator Explorer tree directly within Solution Explorer. You can set breakpoints inside generated files, inspect generated C♯ source code in real time, and step through generated serialization pipelines during active debug sessions.
Developer productivity is directly tied to inner-loop compilation speed. In massive enterprise solutions spanning dozens of microservices and hundreds of class libraries, waiting for full builds drains developer momentum.
Visual Studio 2026 introduces a multi-tier build optimization engine that reduces incremental build times by up to 65% on modern multi-core processors:
For developers who maintain complex web development pipelines alongside backend APIs, pairing these swift compilation cycles with our 30 essential Chrome extensions for developers helps streamline end-to-end full-stack testing.
Cloud-native microservice architectures demand tools that can orchestrate, trace, and debug distributed containers without requiring developers to manage complex YAML files or terminal scripts manually.
Visual Studio 2026 embeds the .NET Aspire orchestration dashboard directly into the IDE interface. With a single click (`F5`), Visual Studio spins up distributed application stacksâincluding Redis caches, PostgreSQL containers, RabbitMQ queues, and OpenTelemetry endpointsâand visualizes real-time request traces, logs, and structured health metrics inside a unified docking pane.
Hot Reload has been significantly upgraded in Visual Studio 2026 to support edits that previously required a full restart. Developers can now modify generic methods, alter asynchronous state machine logic, add lambda expressions with captured variables, and edit Blazor WebAssembly components with near-instant hot swapping.
The major highlights include GitHub Copilot Agent Mode for multi-file refactoring, native zero-overhead .NET 10 memory profiling, first-class C♯ 14 language tooling, up to 65% faster builds via parallel MSBuild, and integrated .NET Aspire cloud dashboard tooling.
Copilot Agent analyzes the entire solution dependency tree to plan and apply edits across multiple files simultaneously, such as updating models, interfaces, controllers, and test files in a single reviewable transaction.
Yes. Visual Studio 2026 provides complete day-one support for C♯ 14 language syntax (such as the `field` keyword for auto-properties) and .NET 10 runtime SDKs and project templates.
Yes. Visual Studio 2026 features a built-in Source Generator Explorer that displays all generated source files in Solution Explorer, allowing developers to set breakpoints and inspect generated code during execution.
The new Diagnostics Hub memory profiler uses hardware-accelerated EventPipe technology in .NET 10, delivering zero noticeable performance degradation during active debugging sessions.
It leverages out-of-process parallel MSBuild compilation, local artifact caching, and signature-based dependency evaluation to skip redundant builds and accelerate incremental compilation by up to 65%.
Hot Reload 2.0 supports edits to generic types, asynchronous state machines, captured lambda variables, and Blazor WebAssembly components without requiring an application restart.
Yes. Visual Studio 2026 provides an extensible AI provider interface allowing enterprise teams to connect local LLMs via Ollama, ONNX Runtime, or private Azure OpenAI instances.
Yes. .NET Aspire orchestration, container management, and distributed OpenTelemetry telemetry dashboards are fully embedded within the IDE's debugging windows.
Yes. Visual Studio 2026 provides full backward compatibility for building, testing, and debugging solutions targeting .NET 8, .NET 9, and legacy .NET Framework 4.8.x projects.
Visual Studio 2026 delivers an unmatched developer experience by blending autonomous AI agent workflows with relentless inner-loop speed and deep runtime mechanical sympathy. By eliminating routine boilerplate through C♯ 14 tooling, streamlining multi-file refactoring with Copilot Agent Mode, and providing real-time memory diagnostics without performance degradation, the IDE sets a new benchmark for software engineering productivity.
Whether you are building high-throughput microservices in .NET 10, architecting cloud-native distributed topologies with .NET Aspire, or modernizing enterprise legacy applications, upgrading to Visual Studio 2026 equips your team with the most potent development environment ever built.
Have you installed Visual Studio 2026 in your development workflow yet? Which feature has made the biggest impact on your daily productivityâCopilot multi-file edits, parallel MSBuild acceleration, or native memory diagnostics? Share your experiences and thoughts in the comments below!
In 2025, I wrote that software engineers ought to be assessed by âvalue over replacementâ: not how much money they made for their company, but how much they would have made compared to the average engineer in their position. Iâve always found it vaguely silly when engineers put âbuilt a product that made $Xâ on their resumes, when they just did the JIRA tickets that came across their desk.
Today, value over replacement is even more important. A replacement-level engineer in the 2010s was fine: maybe not worth promoting, but still worth paying, because writing code had a high fixed cost. Now writing code costs a hundred bucks a month. What are you doing that GPT-5.6-Sol or Claude Opus 5 wouldnât do in your position? Why is it worth paying an extra two or three orders of magnitude for?
This is a scary thought. But youâre not doing yourself any favors by pretending that LLMs canât actually write code and itâs all just a scam, or that LLM-written code is inherently so bad as to cause companies using it to collapse next year. We are not going to wake up in 2027 to find that the AI craze is over and everyone is writing code by hand again. You ought to put some serious thought into what you can do better than the models in the medium and long term.
Staying ahead of the models is a moving target. At the start of 2026, âmake working changes to large codebasesâ was in this category, but now itâs not. For this reason, I doubt that you can retreat to some âhard engineeringâ area that requires deeper expertise. That might work in the short term, but not forever. If LLMs can find a better lower bound on the Riemann hypothesis, they will soon1 be able to write solid high-performance kernel drivers or GPU shaders or whatever.
I think itâs more useful to look at the tasks models havenât gotten better at over time, and the tasks that are hard for them get better at in principle. The two best examples of these are:
What do frontier LLMs get wrong? What kind of coding mistakes do they make? Itâs been a long time since Iâve seen a straight-up hallucination from a coding agent, or a simple logic error like an off-by-one. The mistakes they make tend to be errors of ignorance:
Other times theyâre errors of paranoia:
What do these errors have in common? Theyâre the kind of errors a smart engineer might make if they had no context on the system: theyâre competent enough to be able to solve the problem, but they havenât been around long enough to confidently say âyes, we can take this risk to avoid an extra three thousand lines of codeâ. Until someone cracks continuous learning or truly massive context windows, this is just an inherent feature of how AI agents operate. If you can catch these errors, youâll be providing real value.
The only way to catch these errors is to be familiar with the codebase and familiar with the system in general. For much more on this, see my post You canât design software you donât work on. But thereâs also a psychological component to it. You have to be willing to confidently disagree with the agent.
AI agents can be very convincing. Often they can get âstuckâ on some error above where theyâre not willing to take a particular risk, so they keep going back and sneaking in code to cover that case (or writing persuasive arguments about why that case is important). To add value, you need to be willing to say âthis sucks, I donât think we need X and Y at all, why canât we do Z in a much simpler way?â It takes courage.
You canât rely on other AI agents to review each otherâs work. If you use the same model, itâll reliably make the exact same assumptions and mistakes. But even if you use different models, theyâll also tend towards the same kinds of mistakes â ignorance and paranoia â for the same structural reasons. AI-driven review loops are in fact more likely to get these things wrong, because modern AIs have been RL-ed to try to find a few nitpicks no matter what. Having a critic AI and a worker AI bounce off each other is a really good way to end up with ten thousand lines of paranoid slop.
Another area where you can add value on top of AI is communication. Newer models are better at coding, but are paradoxically getting worse at writing. GPT-3.5 and GPT-4 had a human-like writing style at times. GPT-4o introduced the modern slop idiolect, and the newer Anthropic models speak âClaudishâ: a bizarre semi-baroque semi-truncated way of communicating that nobody enjoys. There have been a few bright spots â GPT-4.5 was okay, and I quite liked o32 â but in general LLMs are not good at this. Hereâs two reasons why.
First, good writing is not a verifiable domain. If you want a model to get good at mathematics or coding, you can generate problems for it and automatically grade them. You canât grade good writing. If you try to get humans to grade it â for instance, via the early OpenAI RLHF attempts â you get the kind of writing that sounds impressive to the average person when consumed in single-paragraph form. This is the origin of the âstick three hundred writing devices into every sentenceâ style. I think itâd be possible in principle to hand-pick some people with good taste and have them do it, but there are some obvious problems3 that prevent this from happening.
Second, the labs have been monomaniacally focused on capability instead of communication. When youâre trying to train a model that can break new scientific ground or replace a software engineer, you might trade off some communication ability. In fact, I think we can identify exactly how this has been happening. If you look at internal model reasoning tokens, they tend to have strange word choices and oddly truncated grammar:
RESOLUTION: charge the current-legâs OWN saved-prefix occupancy EAGERLY: when leg i saves e1..et: ALSO commit their occupancy AT LEG i
If you were to translate this into proper English, you would probably end up with something that reads like Claudish:
Charge the current-legâs saved-prefix occupancy on a clean, eager path: when leg i saves e1..et, commit the occupancy at leg i.
I suspect that the weirdly alien writing style of some LLMs is because youâre reading a semi-literal translation of that modelâs internal chain-of-thought, which has become nearly incomprehensible in pursuit of better problem-solving abilities. It is surprisingly hard to translate Claudish to good English: not only do you need to follow the convoluted, compressed language of the original, but you need the technical ability to understand the problem the model is solving.
Because of all this, technical communication may be a surprisingly durable skill. In Peter Wattsâ novel Blindsight, the world is full of cognitively augmented humans. The main character is a âsynthesistâ: someone whose job is to be a translation layer between these geniuses (who speak in abbreviations and gestures) and everyone else. Wattsâ idea is that communication ability may be largely independent from â or even negatively correlated with â intelligence. A âcountry of geniusesâ may still need a bunch of ordinary smart people to translate their insights for everyone else.
If youâre trying to communicate to humans, there are also huge advantages to having a human write the content. Many of us are becoming AI-blind: developing an instinctive reflex that stops us reading when we encounter AI-generated content. Itâs like the reflex that allows people to ignore flashing billboards or sidebar advertisements on websites. If you circulate some planned technical strategy as an AI-written document, most of your colleagues will have to physically force themselves to read it word-by-word.
Whatever you do, donât be a meat proxy: someone who simply copies requests into an AI agent and submits their output as your own work product. Doing that is just begging to be fired, since youâre definitionally not adding any value yourself. Even if you have a cunning system of multiple agents â the so-called âsoftware factoryâ â youâre still on dangerous ground. When the features of your system work their way into enterprise AI tooling (and they will), youâll be disposable.
You need to find some way to leverage your expertise to do what the models canât. Simply not using AI at all is better than being a meat proxy, since youâll probably do some things better than the model would have, but itâs far better to figure out what AI can do and position yourself to fill those gaps. Right now, there are two main gaps: familiarity with the technical details of the system, and the ability to clearly and persuasively write about those details.
If youâre thinking âbut LLMs can do these things now!â, substitute your preferred example of high-difficulty software engineering.
â©Although this was probably a âthank God it doesnât speak like 4oâ reaction.
â©Defining good taste is hard, thereâs no guarantee that AI lab researchers have good taste to start with, nobody will agree on examples, the bulk of users might not even like it, you wonât be able to get enough people to produce the volume of data you need, and so on.
â©Learn how writers use these literary devices – The MacGuffin and Chekhovâs Gun – to create suspense, drive plots, and keep readers hooked.
Here are two literary devices you could use when plotting your next suspense novel, detective story, crime caper, or mystery. From two masters of the craft, these two strategies can help strengthen your storyline.
Alfred Hitchcock gives us The MacGuffin. The MacGuffin is an object, event, or character that seems to be the central focus of the plot at the start of the story but, ultimately, proves to be unimportant. An heiressâs jewels are stolen, but the real plot is the detectiveâs desire for her despite her dark past. A driver must deliver a package cross country, but what is in the package is not important. Mysterious lights are seen over a rural sky the night a boy goes missing, but the story is not about evidence of UFOs but about a community coming together after a tragedy.
Example: The Maltese Falcon by Dashiell Hammett: The valuable Maltese Falcon statuette drives the characters to pursue, deceive, and betray one another. But the object is less important than what the characters are willing to do to obtain it.
Anton Chekhov gives us Chekhov’s Gun. Chekhovâs Gun is the opposite of the MacGuffin. In this storytelling device, the writer unobtrusively introduces an object early on in the storyâbut its significance does not become clear until much later, usually in the climax. The treasured penknife the widow finds in her husbandâs desk is used to help free her from a kidnapperâs ropes. The plastic toy the heroâs child gets with his take-away meal is tested to prove a plastic company is poisoning the environment. [Suggested reading:Â How Chekhovâs Gun Can Help You With Description]
Example: And Then There Were None by Agatha Christie: Early in the novel, 10 people in a house discover a nursery rhyme describing 10 deaths. It seems like an eerie decoration, but then it becomes a blueprint for the murders.
The two devices work at opposite ends of the plot scale. In the first, you deliberately mislead the reader by using something showy and intriguing to get the story started. In the second, you carefully plant a clue that will become important at the end of the story. You can, of course, use these devices in any genre.
The MacGuffin and Chekhovâs Gun may seem like small storytelling devices, but they can have a big impact on your plot. One gives your characters something to chase, while the other plants something that will matter later. Use them carefully, and you can create curiosity and build suspense.

by Anthony Ehlers
Top Tip: Sign up for our free daily writing links.
The post The MacGuffin And Chekhovâs Gun: 2 Literary Devices Writers Should Know appeared first on Writers Write.