[{"data":1,"prerenderedAt":1108},["ShallowReactive",2],{"post-en-narrowed-not-superseded":3,"related-en-narrowed-not-superseded":661},{"id":4,"title":5,"body":6,"categories":642,"coverImage":644,"date":645,"description":646,"extension":647,"lang":648,"legacy":649,"meta":650,"navigation":651,"path":652,"seo":653,"stem":654,"sticky":649,"tags":655,"translation":659,"updated":659,"__hash__":660},"articles/en/narrowed-not-superseded.md","BullMQ in Rust: what the official client leaves me to build",{"type":7,"value":8,"toc":633},"minimark",[9,13,21,34,39,42,52,55,70,111,145,148,152,158,166,199,214,218,230,236,249,256,260,502,511,515,521,532,538,542,548,559,562,565,569,629],[10,11,12],"p",{},"The team that makes BullMQ shipped an official Rust client. The community port I've contributed to since before that client existed beats it on parallel adds — about 118,000 jobs a second to its 95,000, same machine, same Redis, same payloads.",[10,14,15,16,20],{},"That number is the point of this piece. Not because a benchmark win decides which client you should use — it doesn't, and I'll tell you to pick the official one for most systems anyway — but because of who found the faster path and how. ",[17,18,19],"code",{},"bullmq-rs"," is not the work of the BullMQ team. It's a port, built from the outside, from nothing but the wire format and the published Lua scripts, months before an official Rust client was available. I put real work into that wire compatibility: the scripts, the state transitions, the parts of the protocol a README never mentions.",[10,22,23,24,33],{},"So when ",[25,26,30],"a",{"href":27,"rel":28},"https://bullmq.io/news/260712/rust-release/",[29],"nofollow",[17,31,32],{},"bullmq-official"," shipped, the reasonable assumption was that the port was finished — superseded by the people who own the protocol. I went looking for a real answer instead of a comfortable one, and the answer turned out to be more specific: on the axis that belongs to the owners, the port never had a chance. On the axes where it was free to think for itself, it's ahead — in one place, measurably.",[35,36,38],"h2",{"id":37},"what-fresh-eyes-found","What fresh eyes found",[10,40,41],{},"Porting a system you didn't design forces an unusual way of reading it. The original team carries five years of accumulated intent; a porter has only what crosses the wire. Nothing is habit yet. Every command the client sends has to be justified from scratch — and sometimes the justification comes up short, so you do it differently.",[10,43,44,45,47,48,51],{},"That's how ",[17,46,19],{}," ends up reserving job ids the way it does: its bulk path claims every id it needs with a single ",[17,49,50],{},"INCRBY",", one fewer Redis command per job than the official client spends. Nobody told the port how it was supposed to be done, so it did something the original never bothered to.",[10,53,54],{},"It's also how it ends up ahead on parallel adds. When a producer fires adds concurrently, the port's connection multiplexes them measurably better than the official client's — the number at the top of this piece. The plumbing an outsider built from scratch, holding up against plumbing the protocol's owners had years to tune, and winning on this path.",[10,56,57,58,61,62,65,66,69],{},"And it's how the worker ended up generic where the official one deliberately isn't: ",[17,59,60],{},"Worker\u003CT>"," / ",[17,63,64],{},"Job\u003CT>",", with ",[17,67,68],{},"T"," deserialized into your own Rust type.",[71,72,77],"pre",{"className":73,"code":74,"language":75,"meta":76,"style":76},"language-rust shiki shiki-themes github-light github-dark","#[derive(Serialize, Deserialize)]\nstruct SendInvoice {\n    customer_id: CustomerId,\n    invoice_id: InvoiceId,\n}\n","rust","",[17,78,79,87,93,99,105],{"__ignoreMap":76},[80,81,84],"span",{"class":82,"line":83},"line",1,[80,85,86],{},"#[derive(Serialize, Deserialize)]\n",[80,88,90],{"class":82,"line":89},2,[80,91,92],{},"struct SendInvoice {\n",[80,94,96],{"class":82,"line":95},3,[80,97,98],{},"    customer_id: CustomerId,\n",[80,100,102],{"class":82,"line":101},4,[80,103,104],{},"    invoice_id: InvoiceId,\n",[80,106,108],{"class":82,"line":107},5,[80,109,110],{},"}\n",[10,112,113,114,117,118,121,122,125,126,125,129,132,133,136,137,139,140,144],{},"The official processor hands you a ",[17,115,116],{},"Job"," and expects a ",[17,119,120],{},"serde_json::Value"," back — friendlier for FFI and cross-language neutrality, but schema validation becomes your problem. The trade-off is real: a heterogeneous queue (",[17,123,124],{},"send-email",", ",[17,127,128],{},"generate-pdf",[17,130,131],{},"sync-customer",") maps more naturally to the untyped ",[17,134,135],{},"Value",", and with ",[17,138,19],{}," you'd wrap the variants in an enum — usually a ",[141,142,143],"em",{},"good"," design, but a stronger opinion than BullMQ itself imposes. For a Rust-only service, the typed side is simply the better API. An official client couldn't have made that choice: it has to stay legible across Node, Python, Elixir, and Rust at once. A fork answers to one language, so it gets to be opinionated.",[10,146,147],{},"None of these came from being smarter than the BullMQ team. They came from not being the BullMQ team.",[35,149,151],{"id":150},"what-ownership-still-wins","What ownership still wins",[10,153,154,155,157],{},"The counterweight is just as concrete: ",[17,156,32],{}," owns the protocol.",[71,159,164],{"className":160,"code":162,"language":163,"meta":76},[161],"language-text","BullMQ changes protocol\n       │\n       ├──► bullmq-official\n       │      same repository, same scripts, coordinated release\n       │\n       └──► bullmq-rs\n              notice → port scripts → update marshalling → update tests → release\n","text",[17,165,162],{"__ignoreMap":76},[10,167,168,169,172,173,125,176,125,179,125,182,125,185,188,189,191,192,195,196,198],{},"Both libraries are clients around a Redis state machine of Lua scripts. The hard part of BullMQ isn't ",[17,170,171],{},"Queue::add()","; it's preserving atomic transitions across ",[17,174,175],{},"wait",[17,177,178],{},"active",[17,180,181],{},"delayed",[17,183,184],{},"completed",[17,186,187],{},"failed",", priority sets, locks, dedup keys, schedulers, rate-limit keys, and parent dependencies. ",[17,190,32],{}," lives in the main BullMQ repo and uses the ",[141,193,194],{},"same scripts"," as the Node, Python and Elixir clients — change a script and the Rust client ships in the same release. ",[17,197,19],{}," independently vendors those scripts and has to chase every upstream change forever. It does that well (23 ported scripts, bidirectional Node↔Rust CI), but \"forever\" is the cost, and over five years it becomes the whole maintenance budget.",[10,200,201,202,207,208,213],{},"Fresh eyes cut the other way here too. Auditing our own worker loop, I found a double-execution race: under saturation, a job claimed before the worker had capacity to run it could outwait its Redis lock, get re-queued by the stalled-job check, and execute twice — emails sent twice, cards charged twice. I wrote a ",[25,203,206],{"href":204,"rel":205},"https://github.com/bogardt/bullmq-rs/blob/ea1e3457be366ed004f885095753be1a32e49327/tests/lock_renewal_race.rs",[29],"failing test that reproduces it"," and ",[25,209,212],{"href":210,"rel":211},"https://github.com/bogardt/bullmq-rs/pull/21",[29],"proposed the fix"," — reserve capacity first, then claim, the ordering the official client already had. Routine maintenance a fork owes its users, and a reminder of what carrying a distributed-state protocol without its designers actually means: in a queue, the client is not a thin wrapper around Redis. It participates in the protocol that decides whether work is claimed, retried, recovered, or run again.",[35,215,217],{"id":216},"the-benchmark-nobody-had-run","The benchmark nobody had run",[10,219,220,221,223,224,229],{},"The official announcement benchmarks its Rust client against Node.js (≈6,900 jobs/s sequential, up to 27,000 jobs/s at high concurrency on an M2 Pro), not against ",[17,222,19],{},". There was no head-to-head between the two, so I ran one — same Redis, same payloads, same scenarios as the official suite, raw data committed: ",[25,225,228],{"href":226,"rel":227},"https://github.com/enricodeleo/bullmq-rust-bench",[29],"bullmq-rust-bench",".",[10,231,232,233,235],{},"On an M4, the median of five runs: the official client leads sequential adds (≈18.3k vs ≈13–15k jobs/s); ",[17,234,19],{}," wins parallel adds (≈118k vs ≈95k); and under a steady producer both sit at sub-millisecond p50 latency. These are localhost measurements of client-side overhead, not production capacity figures.",[10,237,238,239,241,242,244,245,248],{},"Bulk adds are where five years of profiling shows. ",[17,240,19],{}," is cleverer on paper there too — the single-",[17,243,50],{}," id reservation, one fewer Redis command per job — and still loses 2.6×. The reason is scheduling, not the wire: the port's ",[17,246,247],{},"add_bulk"," awaits each job's script call in a for-loop, one round trip at a time, while the official client builds every call up front and runs them concurrently over its multiplexed connection — its source even says so: \"Run all add_job calls concurrently since the connection is multiplexed.\" The port has the same capability, proven by its own parallel-add win, and doesn't spend it in its own bulk path. Design insight and profiling discipline are different assets. The fork has the first; the owners have the second.",[10,250,251,252,255],{},"The other dramatic gap — draining a pre-filled queue — is a single 5-second ",[17,253,254],{},"BZPOPMIN"," cold-start stall in the port's fetch loop, not slow processing; once it wakes, it drains at a rate comparable to the official client. And Redis commands per job came out identical everywhere, which is worth sitting with: the official client's edge is not wire efficiency, it is orchestration — except on the one path where the port already orchestrates better.",[35,257,259],{"id":258},"the-rest-in-one-table","The rest, in one table",[261,262,263,284],"table",{},[264,265,266],"thead",{},[267,268,269,273,277,281],"tr",{},[270,271,272],"th",{},"Dimension",[270,274,275],{},[17,276,32],{},[270,278,279],{},[17,280,19],{},[270,282,283],{},"Edge",[285,286,287,305,320,340,355,377,393,410,424,443,457,471,486],"tbody",{},[267,288,289,293,296,299],{},[290,291,292],"td",{},"BullMQ wire compatibility",[290,294,295],{},"Canonical upstream",[290,297,298],{},"Independently maintained",[290,300,301],{},[302,303,304],"strong",{},"Official",[267,306,307,310,313,316],{},[290,308,309],{},"Lua/state-transition correctness",[290,311,312],{},"Same upstream scripts, same repo",[290,314,315],{},"Ported/vendored v5 scripts",[290,317,318],{},[302,319,304],{},[267,321,322,325,329,336],{},[290,323,324],{},"Rust payload typing",[290,326,327],{},[17,328,120],{},[290,330,331,332,61,334],{},"Generic ",[17,333,64],{},[17,335,60],{},[290,337,338],{},[302,339,19],{},[267,341,342,345,348,351],{},[290,343,344],{},"Parallel-add throughput (measured)",[290,346,347],{},"≈95k jobs/s",[290,349,350],{},"≈118k jobs/s",[290,352,353],{},[302,354,19],{},[267,356,357,360,363,373],{},[290,358,359],{},"Worker return values",[290,361,362],{},"Native JSON return value",[290,364,365,366,369,370],{},"Handler returns ",[17,367,368],{},"Result\u003C()>",", return hardcoded ",[17,371,372],{},"{}",[290,374,375],{},[302,376,304],{},[267,378,379,382,385,388],{},[290,380,381],{},"Worker fetch architecture",[290,383,384],{},"Concurrency slot before fetch; N loops",[290,386,387],{},"Central loop; claim before capacity in v2.2.0 (fix pending)",[290,389,390],{},[302,391,392],{},"Official today",[267,394,395,400,403,406],{},[290,396,397],{},[17,398,399],{},"wait_until_finished",[290,401,402],{},"Intentionally absent",[290,404,405],{},"Implemented",[290,407,408],{},[302,409,19],{},[267,411,412,415,417,420],{},[290,413,414],{},"Dynamic concurrency",[290,416,405],{},[290,418,419],{},"Fixed builder concurrency",[290,421,422],{},[302,423,304],{},[267,425,426,433,436,439],{},[290,427,428,429,432],{},"TLS (",[17,430,431],{},"rediss://",")",[290,434,435],{},"Supported",[290,437,438],{},"Not in current manifest",[290,440,441],{},[302,442,304],{},[267,444,445,448,450,452],{},[290,446,447],{},"Worker-level rate limiting and metrics",[290,449,405],{},[290,451,405],{},[290,453,454],{},[302,455,456],{},"Tie",[267,458,459,462,464,467],{},[290,460,461],{},"Dynamic/global limits, Prometheus, worker inspection",[290,463,405],{},[290,465,466],{},"Missing or narrower",[290,468,469],{},[302,470,304],{},[267,472,473,476,479,482],{},[290,474,475],{},"Long-term maintenance risk",[290,477,478],{},"Low protocol-drift risk",[290,480,481],{},"Must chase upstream",[290,483,484],{},[302,485,304],{},[267,487,488,491,494,497],{},[290,489,490],{},"Rust-runtime maturity",[290,492,493],{},"Very new",[290,495,496],{},"Independent track record",[290,498,499],{},[302,500,501],{},"Too early to call",[10,503,504,505,507,508,510],{},"For completeness: the official client also leads on dynamic/global rate limiting, Prometheus export, and worker inspection; ",[17,506,19],{}," uniquely ships ",[17,509,399],{},", which the official client omits on purpose as a production footgun.",[35,512,514],{"id":513},"so-which-should-you-actually-use","So which should you actually use?",[10,516,517,520],{},[302,518,519],{},"Use the official client"," when BullMQ compatibility is the contract, when workers need to coexist across languages, or when you do not want to validate Redis-level compatibility yourself. The win is not that its Rust is inherently better today — on at least one measured path it is not — but that one team owns the protocol and every client.",[10,522,523,528,529,531],{},[302,524,525,526],{},"Use ",[17,527,19],{}," when the application is mostly Rust, when compile-time typed payloads matter to you, when your producers add in parallel, or when you're willing to own protocol-conformance testing. Its ",[17,530,64],{}," and its parallel-add path are real advantages, not cosmetic ones.",[10,533,534,535,537],{},"And if you already run ",[17,536,19],{}," in production, don't migrate just because an official crate appeared. The official Redis semantics have pedigree, but its Rust orchestration is brand new.",[35,539,541],{"id":540},"what-im-doing-with-bullmq-rs-from-here","What I'm doing with bullmq-rs from here",[10,543,544,545,547],{},"The first item is the one the benchmark hands me: make ",[17,546,247],{}," use the multiplexing the parallel-add path already proves — build every call up front, run them concurrently. The capability exists; it just isn't spent everywhere yet.",[10,549,550,551,554,555,558],{},"Then the typed side of the contract: typed input ",[141,552,553],{},"and"," typed output (",[17,556,557],{},"Worker\u003CTInput, TOutput>","), ergonomic enums, structured errors, Tokio-native cancellation — the places a single-language client can move faster and be more opinionated than one built to stay uniform across four runtimes.",[10,560,561],{},"Parity with upstream stays table stakes: version-map releases to BullMQ, port every script change, keep the bidirectional CI honest, ship fixes like the race repair. That work doesn't justify the fork. The other work does.",[10,563,564],{},"Not superseded — narrowed. The state machine belongs to the team that wrote it, and for most systems their client is the right default. What the port keeps is the thing it had before there was an official client at all: the freedom to look at the same wire with no habits, and sometimes find the faster way through it.",[35,566,568],{"id":567},"sources","Sources",[570,571,572,579,590,600,609,615,622],"ul",{},[573,574,575],"li",{},[25,576,578],{"href":27,"rel":577},[29],"Announcing BullMQ for Rust (official)",[573,580,581,589],{},[25,582,585,586],{"href":583,"rel":584},"https://github.com/taskforcesh/bullmq/blob/master/rust/src/worker.rs",[29],"taskforcesh/bullmq — ",[17,587,588],{},"rust/src/worker.rs"," (official worker architecture)",[573,591,592],{},[25,593,596,597],{"href":594,"rel":595},"https://github.com/bogardt/bullmq-rs/blob/main/src/worker.rs",[29],"bogardt/bullmq-rs — ",[17,598,599],{},"src/worker.rs",[573,601,602],{},[25,603,596,606],{"href":604,"rel":605},"https://github.com/bogardt/bullmq-rs/blob/main/BULLMQ_V5_PARITY.md",[29],[17,607,608],{},"BULLMQ_V5_PARITY.md",[573,610,611],{},[25,612,614],{"href":210,"rel":613},[29],"bogardt/bullmq-rs — fix PR #21",[573,616,617],{},[25,618,621],{"href":619,"rel":620},"https://github.com/bogardt/bullmq-rs",[29],"bogardt/bullmq-rs (repo)",[573,623,624,628],{},[25,625,627],{"href":226,"rel":626},[29],"enricodeleo/bullmq-rust-bench"," (the head-to-head benchmark: methodology, raw data, summary tables)",[630,631,632],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":76,"searchDepth":89,"depth":89,"links":634},[635,636,637,638,639,640,641],{"id":37,"depth":89,"text":38},{"id":150,"depth":89,"text":151},{"id":216,"depth":89,"text":217},{"id":258,"depth":89,"text":259},{"id":513,"depth":89,"text":514},{"id":540,"depth":89,"text":541},{"id":567,"depth":89,"text":568},[643],"dev","https://i2.wp.com/enricodeleo.s3.eu-south-1.amazonaws.com/images/bullmq-rust-official-vs-bullmq-rs-cover.png","2026-07-19T09:00:00.000Z","BullMQ shipped an official Rust client. The community port I helped build still beats it on parallel adds. On what fresh eyes can and cannot win.","md","en",false,{},true,"/en/narrowed-not-superseded",{"title":5,"description":646},"en/narrowed-not-superseded",[75,656,657,658],"redis","bullmq","queues",null,"r8EId41YspRFZHnzyCNW4KW8ISE32fxJ0LbWM_wAUyo",[662,1026],{"id":663,"title":664,"body":665,"categories":1012,"coverImage":1014,"date":1015,"description":1016,"extension":647,"lang":648,"legacy":649,"meta":1017,"navigation":651,"path":1018,"seo":1019,"stem":1020,"sticky":649,"tags":1021,"translation":679,"updated":659,"__hash__":1025},"articles/en/speed-without-judgment.md","Speed without judgment",{"type":7,"value":666,"toc":1003},[667,672,682,685,692,695,698,704,711,715,718,725,739,746,752,755,759,765,772,775,786,789,796,804,807,811,814,821,832,835,842,847,850,853,860,864,867,870,873,876,882,885,890,893,897,903,906,913,916,927,931,937,940,951,956,959,962,967,971,974,981,984,995,998],[10,668,669],{},[141,670,671],{},"Vibe coding didn't invent badly written software. It just removed the friction.",[10,673,674],{},[141,675,676,677,681],{},"(This is an English adaptation of my Italian essay ",[25,678,680],{"href":679},"/velocita-senza-giudizio","Velocità senza giudizio",".)",[10,683,684],{},"I once saw a backend with authentication hardcoded into the frontend. Admin password in plain text, visible to anyone who opened DevTools. That wasn't vibe coding — it was 2018, human devs, hired off Upwork.",[10,686,687,688,691],{},"I mention it because what's happening now ",[302,689,690],{},"isn't new",". It's the same thing, faster.",[10,693,694],{},"I've seen Google Sheets used as a database. It collapsed at four concurrent users. The founders were surprised.",[10,696,697],{},"I've seen features declared \"done\" that were empty API endpoints with nothing behind them. I've seen human developers do it. I've seen Claude Opus 4.6 — Anthropic's most capable model right now — do it too. I've seen tests written to pass, not to verify.",[699,700,701],"blockquote",{},[10,702,703],{},"A system that certifies itself is more dangerous than one that fails openly.",[10,705,706,707,710],{},"The problem isn't the AI. It's that the speed it offers can make you forget that building software is ",[302,708,709],{},"fundamentally a cognitive act"," — not a writing one.",[35,712,714],{"id":713},"software-is-not-text","Software is not text",[10,716,717],{},"This is the thing the enthusiasm around vibe coding systematically forgets.",[10,719,720,721,724],{},"Software is not text. It's a ",[302,722,723],{},"formal model of reality"," — a system of rules describing how a domain behaves, how states change, how exceptions are handled, how time, concurrency, and failure are treated.",[10,726,727,728,731,732,735,736,229],{},"Writing code was never the hard problem. The hard problem is understanding ",[141,729,730],{},"what"," to write — and ",[141,733,734],{},"why",". It's identifying the domain's boundaries, the invariants that must hold, the edge cases the business never told you about because it didn't know it had to, the implications a given architectural choice will have six months from now when the requirements change — ",[302,737,738],{},"because they always change",[10,740,741,742,745],{},"That understanding isn't acquired quickly. It's acquired through iteration, conversation, mistakes, corrections, nights spent wondering why the thing isn't behaving the way it should. It's what Michael Polanyi called ",[141,743,744],{},"tacit knowledge"," — knowing that can't be fully transferred into words, that lives in accumulated experience, in recognized patterns, in intuitions built across dozens of projects.",[10,747,748,751],{},[302,749,750],{},"The AI doesn't have this knowledge."," It has the statistical average of what's been written on the internet about problems similar to yours.",[10,753,754],{},"Which is not the same thing.",[35,756,758],{"id":757},"the-trap-of-apparent-perfection","The trap of apparent perfection",[10,760,761,762,229],{},"There's something specific about AI that makes all of this worse: ",[302,763,764],{},"the code it produces always looks well written",[10,766,767,768,771],{},"It's well named, has comments in the right places, follows conventions, uses the correct patterns. It doesn't look like the code of someone who doesn't know what they're doing — it looks like the code of someone who knows ",[141,769,770],{},"exactly"," what they're doing.",[10,773,774],{},"That's the problem.",[10,776,777,778,781,782,785],{},"Badly written wrong code stops you. You see it, you feel it, it makes you suspicious. It triggers your critical instinct before you've even understood what it does. ",[302,779,780],{},"Well-written wrong code"," sails through code review without tripping a single alarm, passes the tests, ships to production. And when it breaks — ",[141,783,784],{},"because it breaks"," — its correct-looking form makes it harder, not easier, to locate the conceptual problem underneath.",[10,787,788],{},"You're looking for a bug in code that doesn't look buggy. You're looking for a wrong assumption in code that looks reasonable.",[10,790,791,792,795],{},"In 1986 Fred Brooks wrote that the essential difficulty of software isn't accidental — it isn't about syntax, compilers, typing speed. It's about the ",[302,793,794],{},"intrinsic conceptual complexity"," of the systems we build. Forty years later, AI has all but eliminated the accidental difficulty. The essential one is untouched.",[699,797,798],{},[10,799,800,803],{},[141,801,802],{},"\"No silver bullet.\"","\n— Fred Brooks, 1986",[10,805,806],{},"And now that the accidental difficulty is gone, the essential difficulty is more exposed than ever. But it's also easier not to see, because the output hiding it looks so convincing.",[35,808,810],{"id":809},"the-model-of-reality","The model of reality",[10,812,813],{},"When you ask AI to build something, what you get back isn't a solution.",[10,815,816,817,820],{},"It's an ",[302,818,819],{},"interpretation"," of the problem you described.",[10,822,823,824,827,828,831],{},"The AI builds a model of reality based on what you told it — and what you told it is inevitably incomplete, because the domain knowledge in your head doesn't transfer whole into a prompt. The gap between the problem you have ",[141,825,826],{},"in your head"," and the problem the AI ",[141,829,830],{},"understood"," is always there. Sometimes it's negligible. Sometimes it's the difference between a system that holds and one that collapses at the first real edge case.",[10,833,834],{},"Speed doesn't give you time to measure that gap. Worse: speed, combined with the apparent perfection of the output, actively creates the illusion that the gap doesn't exist.",[10,836,837,838,841],{},"This is why ",[302,839,840],{},"\"it works\" is never enough"," as an acceptance criterion.",[10,843,844],{},[141,845,846],{},"Works against what?",[10,848,849],{},"Against the tests the AI wrote to pass — built around the same cases it had in mind when it wrote the code, in a closed loop that includes no external reality? Against a demo with clean data and happy paths? Or against the actual domain logic, with all the edge cases only you know about because you've lived inside that domain?",[10,851,852],{},"Salvatore Sanfilippo — antirez, the creator of Redis — raised an analogous problem about code review: the mental state of the person who wrote the code doesn't transfer to the reviewer. The reviewer sees the text, not the reasoning that produced it. Either they flag superficial things, or they don't understand enough to see the real problem.",[10,854,855,856,859],{},"With AI, the problem radicalizes: ",[302,857,858],{},"the AI has no mental state to transfer",". It produced output optimizing for the internal coherence of the text, not for correspondence with your reality. The reviewer — you — has to close that gap alone. If you don't stop to do it, nobody will.",[35,861,863],{"id":862},"let-it-sink","Let it sink",[10,865,866],{},"There's a practice every engineer with enough scar tissue has internalized: sleep on it.",[10,868,869],{},"Not out of laziness. Out of cognitive necessity.",[10,871,872],{},"The human brain doesn't process complex problems linearly and sequentially. It processes in parallel, often below the threshold of consciousness. That's why the best solutions arrive in the shower, in the car, first thing in the morning — not in front of the screen while you're actively hunting for the answer. The things you don't see in the moment surface later, when you come back with fresh eyes and the brain has had time to connect patterns that active focus can't see.",[10,874,875],{},"It's not mysticism. It's neuroscience. The brain's diffuse mode is essential for insight, for deep understanding, for noticing the inconsistencies that focused mode skips because it's too deep inside the problem.",[10,877,878,879],{},"With AI, that practice becomes even more important, and even easier to skip. Because in a few minutes you have something in front of you that looks complete. The effort you spent is minimal, so the sense of investment is low, so the bar for calling it done drops with it. ",[302,880,881],{},"You didn't struggle to get there, so you don't feel the need to protect yourself from the possibility that the struggle was wasted.",[10,883,884],{},"But \"looks complete\" and \"is correct\" are two different things — and the speed at which AI produces output has made that distinction subtler than ever.",[699,886,887],{},[10,888,889],{},"Stopping isn't losing the advantage of speed. It's the moment you verify that speed took you in the right direction.",[10,891,892],{},"Without that moment, you're just going fast toward the wrong place — and you get there sooner.",[35,894,896],{"id":895},"code-review-isnt-enough","Code review isn't enough",[10,898,899,900],{},"The instinctive response to all of this is: ",[141,901,902],{},"\"that's what code review is for.\"",[10,904,905],{},"But code review, as practiced on most teams, doesn't solve the problem we're describing.",[10,907,908,909,912],{},"Traditional code review is great at catching syntax bugs, convention violations, obvious security issues. It's much less effective at evaluating whether the ",[302,910,911],{},"architectural decisions are correct",", whether the domain model reflects reality, whether the implicit assumptions in the code are valid.",[10,914,915],{},"With AI the problem is amplified: the generated code is syntactically unimpeachable. The reviewer scans, finds nothing that trips the standard alarms, approves. The conceptual problem underneath goes through undetected.",[10,917,918,919,922,923,926],{},"Useful code review in this context isn't ",[141,920,921],{},"\"is this code well written?\""," — that answer is almost always yes. It's ",[141,924,925],{},"\"does this code do what it should do in the context of the real domain?\""," — and to answer that you need the domain knowledge the AI doesn't have, you need to have understood the requirements well enough to know what to check, you need to have let the solution sit long enough to see it with critical distance.",[35,928,930],{"id":929},"the-debt-you-dont-see","The debt you don't see",[10,932,933,934,229],{},"Technical debt generated without judgment has one specific trait: ",[302,935,936],{},"it's silent",[10,938,939],{},"It doesn't show up immediately. It accumulates in decisions the AI made by default, that weren't your decisions — but are now in the code, and the code is in production. In architectures that hold for the use case you described but not the next one. In implicit assumptions the AI made filling the gaps in your prompt with the average of what it saw in training.",[10,941,942,943,946,947,950],{},"Ward Cunningham, who coined the term \"technical debt,\" meant something specific: ",[141,944,945],{},"deliberate"," debt, consciously taken on to move faster now with the intention of paying it back later. Not ",[141,948,949],{},"accidental"," debt — the kind you accumulate without knowing it because you didn't understand the problem well enough.",[10,952,953],{},[302,954,955],{},"Deliberate debt is a legitimate tool. Accidental debt is capitalized ignorance.",[10,957,958],{},"Vibe coding without judgment produces almost exclusively accidental debt. Because speed prevents understanding, and without understanding you can't even consciously choose to go into debt — you just go into debt, without knowing it, without being able to quantify it, without a plan to pay it back.",[10,960,961],{},"And when it surfaces, it surfaces at the worst possible time. When there's traction. When there are real users. When the cost of fixing it is an order of magnitude higher than it would have been at the start.",[699,963,964],{},[10,965,966],{},"The bill always arrives. Speed only determines how fast you run it up.",[35,968,970],{"id":969},"speed-is-a-means","Speed is a means",[10,972,973],{},"AI in software development is extraordinary. I use it every day.",[10,975,976,977,980],{},"But speed is not the goal — it's a means. The goal is to build something that ",[302,978,979],{},"reflects the logic you had in your head",", that holds up under the real domain's edge cases, that someone can understand and change a year from now without cursing whoever wrote it.",[10,982,983],{},"If speed keeps you from verifying that this is true, you're not going faster. You're just failing faster. With more lines of code. With more confidence. With less chance of noticing before it becomes expensive.",[10,985,986,987,990,991,994],{},"The difference between an engineer who uses AI well and one who uses it badly isn't technical. ",[302,988,989],{},"It's cognitive."," It's the ability to stop, let things settle, come back with critical distance, and ask: does this actually reflect what I wanted? Or am I looking at something that ",[141,992,993],{},"looks like"," what I wanted?",[10,996,997],{},"Those are uncomfortable questions to ask when you've just had a productive afternoon and the screen is full of new code and the tests are passing.",[10,999,1000],{},[141,1001,1002],{},"Ask them anyway.",{"title":76,"searchDepth":89,"depth":89,"links":1004},[1005,1006,1007,1008,1009,1010,1011],{"id":713,"depth":89,"text":714},{"id":757,"depth":89,"text":758},{"id":809,"depth":89,"text":810},{"id":862,"depth":89,"text":863},{"id":895,"depth":89,"text":896},{"id":929,"depth":89,"text":930},{"id":969,"depth":89,"text":970},[643,1013],"ai","https://i2.wp.com/enricodeleo.s3.eu-south-1.amazonaws.com/images/velocita-senza-giudizio.png","2026-07-12T12:00:00.000Z","Vibe coding didn't invent badly written software. It just removed the friction. And speed, without the time to evaluate, only produces faster mistakes.",{},"/en/speed-without-judgment",{"title":664,"description":1016},"en/speed-without-judgment",[1013,1022,1023,1024],"vibe-coding","engineering","build-in-public","1EiIosWChyFyr3gLffNLNa3AnQzQaH3u9ym3ejZxupc",{"id":1027,"title":1028,"body":1029,"categories":1095,"coverImage":1096,"date":1097,"description":1098,"extension":647,"lang":648,"legacy":649,"meta":1099,"navigation":651,"path":1100,"seo":1101,"stem":1102,"sticky":651,"tags":1103,"translation":1037,"updated":659,"__hash__":1107},"articles/en/a-human-if-needed.md","A human, if needed",{"type":7,"value":1030,"toc":1090},[1031,1039,1042,1045,1048,1051,1055,1058,1061,1067,1071,1074,1077,1081,1084,1087],[10,1032,1033],{},[141,1034,676,1035,681],{},[25,1036,1038],{"href":1037},"/un-umano-se-serve","Un umano, se serve",[10,1040,1041],{},"Every time a new model ships, the feed fills up with reviews. \"I tried it. Here are my impressions.\" Screenshots, homegrown benchmarks, verdicts.",[10,1043,1044],{},"I don't understand what feels special about chatting with the same model a hundred million other people have access to. It's like reviewing electricity.",[10,1046,1047],{},"To be clear: I'm not saying models don't matter. They matter to the point that any frontier LLM is, frankly, already magic. I'm saying the opposite: it's precisely because they're magic available to everyone, at the same price, on the same day, that they've stopped being an interesting topic. When everyone has the same capability, capability stops being the variable.",[10,1049,1050],{},"The variable is what you build around it.",[35,1052,1054],{"id":1053},"the-question-i-care-about","The question I care about",[10,1056,1057],{},"The question I care about isn't which model is best this month. It's what this technology does to organizations. And there I see something specific, something I think is still underrated.",[10,1059,1060],{},"Today companies \"adopt AI\": they take the existing org chart — people, roles, hierarchies — and bolt tools onto people. A copilot for the developer, an assistant for marketing, a chatbot for support. The human stays the default executor; the AI assists.",[10,1062,1063,1064],{},"The AI-native company of tomorrow will do the exact opposite: ",[302,1065,1066],{},"an agent for every role, and a human if needed.",[35,1068,1070],{"id":1069},"the-inverted-org-chart","The inverted org chart",[10,1072,1073],{},"This isn't a provocation. It's the logical consequence of where we already are. If an agent covers the first pass at the code, the first draft of the report, the first response to the customer, the role is no longer a person with a tool: it's an automated process with an escalation point. The org chart inverts. The human stops being the workforce and becomes the layer of judgment — the one you escalate to when judgment actually changes the risk.",[10,1075,1076],{},"That changes very concrete questions. \"Where do we need hands\" becomes \"where do we need judgment.\" And judgment isn't hired, it's accumulated. The people who move up are the ones who have already seen the failure modes, who can tell a plausible output from a correct one, who know when to stop the machine. Thresholds, review, escalation stop being decorative compliance and become the actual design of the organization.",[35,1078,1080],{"id":1079},"the-flip-side","The flip side",[10,1082,1083],{},"There's a flip side too, and it needs saying: an agent per role without a real escalation path isn't an organization, it's automation hiding complexity. And the human \"if needed\" only works if, when needed, they're actually there — with the context, the authority, and the time to intervene. Otherwise you've built an extremely fast factory with an alarm bell nobody hears.",[10,1085,1086],{},"The agent executes. The human responds — in both senses: they respond when needed, and they answer for the consequences.",[10,1088,1089],{},"Everything else — which model, which benchmark, which review — is noise.",{"title":76,"searchDepth":89,"depth":89,"links":1091},[1092,1093,1094],{"id":1053,"depth":89,"text":1054},{"id":1069,"depth":89,"text":1070},{"id":1079,"depth":89,"text":1080},[1013],"https://i2.wp.com/enricodeleo.s3.eu-south-1.amazonaws.com/images/umano-se-serve.png","2026-07-12T11:00:00.000Z","This month's model is a commodity. The real question is what AI does to organizations: an agent for every role, a human if needed.",{},"/en/a-human-if-needed",{"title":1028,"description":1098},"en/a-human-if-needed",[1013,1104,1105,1106,1023],"agents","organization","ai-native","PhZuSu3jlmuZ6PtFT1vI-fLdztj_T-hgZV3LarO_VCg",1784577233401]