# Mihai Serban β€” Full Content > Software engineer sharing insights on JavaScript, React, AWS, mobile development, and building products. Tutorials, deep dives, and project showcases. ## About Hi! I'm Mihai, a Software Engineer from Cluj-Napoca, Romania. I like to consider myself a generalist. Over the course of my career I've had the opportunity to work with a large number of technologies. These days I mostly build Web products using ReactJS and NodeJS, and mobile applications for iOS. Also, I'm a huge PokΓ©mon nerd and coffee addict πŸ˜… Ever since I was a child I had a passion for computers. Here's me at the age of 6, playing on my first computer, a __Intel 80286__. ![Mihai Serban Intel 80286](/images/about/me.jpeg) If you want to learn more about projects I've been involved in, head over to [Projects](https://www.mihaiserban.dev/projects "Projects page"). ## Blog Posts ### 1. Runtime Skill Evals With Pi: Measuring What Agent Skills Actually Change (2026-07-10) URL: https://mihaiserban.dev/blog/runtime-skill-evals-with-pi/ ### 2. A 7K-Parameter Neural Router That Saved Me Money on AI Coding (2026-07-07) URL: https://mihaiserban.dev/blog/neural-llm-router-coding-assistant/ ### 3. Replacing Orchestrator Agents: What arXiv Research Says About Context Bottlenecks and Better Alternatives (2026-07-05) URL: https://mihaiserban.dev/blog/replacing-orchestrator-agents-with-governance-worker-synthesis/ ### 4. Building a Design Pattern Generator for CNC & Laser Cutting (2026-06-09) URL: https://mihaiserban.dev/blog/building-a-design-pattern-generator-for-cnc-laser-cutting/ ### 5. Building Semantic Search Over a Knowledge Base (2026-05-06) URL: https://mihaiserban.dev/blog/building-semantic-search-over-a-knowledge-base/ ### 6. Cloning a Windows drive from Mac OS X using dd (Disk Destroyer) (2021-01-07) URL: https://mihaiserban.dev/blog/cloning-windows-drive-from-mac-os-x-using-dd-disk-destroyer/ ### 7. How to handle AWS SES bounces and complaints (2018-11-01) URL: https://mihaiserban.dev/blog/how-to-handle-aws-ses-bounces-and-complaints/ ### 8. How to fix broken images in React. (2018-10-24) URL: https://mihaiserban.dev/blog/how-to-fix-broken-images-in-react/ ### 9. ES6 cheatsheetβ€Š β€” β€ŠArrow Functions (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-arrow-functions/ ### 10. ES6 cheatsheetβ€Š β€” β€ŠAsync Await (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-async-await/ ### 11. ES6 cheatsheetβ€Š β€” β€ŠClasses (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-classes/ ### 12. ES6 cheatsheet β€Šβ€” β€ŠDestructuring (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-destructuring/ ### 13. ES6 cheatsheet β€Šβ€” β€ŠGenerators (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-generators/ ### 14. ES6 cheatsheet β€Šβ€”β€Š Getter and setter functions (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-getter-and-setter-functions/ ### 15. ES6 cheatsheetβ€Š β€” β€ŠHelpful array functions (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-helpful-array-functions/ ### 16. ES6 cheatsheetβ€Š β€” β€ŠHelpful string functions (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-helpful-string-functions/ ### 17. ES6 cheatsheetβ€Š β€” β€ŠMap & WeakMap (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-map-weakmap/ ### 18. ES6 cheatsheet β€Šβ€” β€ŠModules (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-modules/ ### 19. ES6 cheatsheetβ€Š β€” β€ŠPromises (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-promises/ ### 20. ES6 cheatsheetβ€Š β€” β€ŠSet & WeakSet (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-set-weakset/ ### 21. ES6 cheatsheet β€Šβ€” β€ŠSpread Operator (2018-10-20) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-spread-operator/ ### 22. ES6 cheatsheet β€” String Templates (2018-10-16) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-string-templates/ ### 23. ES6 cheatsheet β€” Variable Declarations (2018-10-16) URL: https://mihaiserban.dev/blog/javascript-es6-cheatsheet-variable-declarations/ ### 24. How I grew my Twitter followers from 500 to 1000 in just 12 days πŸ‘πŸ‘πŸ‘ (2018-05-11) URL: https://mihaiserban.dev/blog/how-i-grew-my-twitter-followers-from-500-to-1000-in-just-12-days/ ### 25. How to generate cryptocurrency time intervals using MongoDB Aggregation Framework and Node.js (2017-08-23) URL: https://mihaiserban.dev/blog/aggregate-mongodb-data-with-node-js-and-mongoose-cryptocurrency-financial-time-series/ ## Projects ### 1. Blitzer.de (2010-05-31) URL: https://mihaiserban.dev/project/blitzer_de/ Technologies: Objective-C, C++, SQLite Platforms: iOS ### 2. Callsign (2018-07-31) URL: https://mihaiserban.dev/project/callsign/ Technologies: ReactJS, Javascript, Redux, CSS3, docker, BABEL, D3.js Platforms: Web ### 3. Creative Navy (2019-06-30) URL: https://mihaiserban.dev/project/creative-navy/ Technologies: ReactJS, Gatsby, CSS3, Javascript Platforms: Web, Mobile ### 4. ELHO Sales Application (2013-07-31) URL: https://mihaiserban.dev/project/elho/ Technologies: Objective-C Platforms: iOS ### 5. ForeverMap (2010-05-31) URL: https://mihaiserban.dev/project/forevermap/ Technologies: Objective-C, C++, CI/CD, Unit testing Platforms: iOS ### 6. Geobrain (2010-05-31) URL: https://mihaiserban.dev/project/geobrain/ Technologies: Objective-C, C++, OpenGL ES Platforms: iOS ### 7. impetus (2018-10-02) URL: https://mihaiserban.dev/project/impetus/ Technologies: ReactJS, NEXT.JS, Javascript, CSS3, CI/CD Platforms: Web ### 8. NextNomads (2014-05-31) URL: https://mihaiserban.dev/project/next_nomads/ Technologies: Objective-C Platforms: iOS ### 9. Scout Global (2010-09-30) URL: https://mihaiserban.dev/project/scout_global/ Technologies: Objective-C, Swift, C++, CI/CD, Unit testing, mongoDB Platforms: iOS ### 10. WeStyle (2013-08-31) URL: https://mihaiserban.dev/project/we_style/ Technologies: Objective-C Platforms: iOS ## Experience - **Independent Contractor** (2017-11-29T00:00+03:00 β€” 2021-11-01T00:00+02:00) - **iOS Developer** at skobbler (aquired by Telenav Inc.) (2010-06-01T00:00+03:00 β€” 2012-08-01T00:00+03:00) - **Mentorship Program** at ITBrainiacs (Apex-Edu & Telenav collaboration) (2014-11-01T00:00+03:00 β€” 2015-05-01T00:00+03:00) - **Paternity Leave/Career break** (2021-11-01T00:00+03:00 β€” 2024-06-01T00:00+02:00) - **Quality Assurance Engineer** at skobbler (acquired by Telenav Inc.) (2009-07-01T00:00+03:00 β€” 2010-06-01T00:00+03:00) - **Senior iOS Developer** at 3Pillar Global (2013-01-01T00:00+03:00 β€” 2013-09-30T00:00+03:00) - **Senior iOS Developer** at Neosteq (2012-08-01T00:00+03:00 β€” 2013-01-01T00:00+03:00) - **Senior Software Engineer - Contractor** at Flutter Entertainment (2019-07-01T00:00+02:00 β€” 2021-11-07T00:00+02:00) - **Senior Software Engineer** at Flutter Entertainment - **Senior Software Engineer** at Telenav Inc. (2014-03-01T00:00+03:00 β€” 2017-11-30T00:00+03:00) ## Full Content ### bio.md Hi! I'm Mihai, a Software Engineer from Cluj-Napoca, Romania. I like to consider myself a generalist. Over the course of my career I've had the opportunity to work with a large number of technologies. These days I mostly build Web products using ReactJS and NodeJS, and mobile applications for iOS. Also, I'm a huge PokΓ©mon nerd and coffee addict πŸ˜… Ever since I was a child I had a passion for computers. Here's me at the age of 6, playing on my first computer, a __Intel 80286__. ![Mihai Serban Intel 80286](/images/about/me.jpeg) If you want to learn more about projects I've been involved in, head over to [Projects](https://www.mihaiserban.dev/projects "Projects page"). --- ### aggregate-mongodb-data-with-node-js-and-mongoose-cryptocurrency-financial-time-series.md ![bitcoin](/images/blog/0_m4PdT9e8rKaVnWvD.jpeg) Cryptocurrency is known for having major price swings, big players often do what’s called a pump and dump. They come into the markets with billions of dollars, buy a lot of tokens and steadily increase the price, create FOMO (fear of missing out), buying pressure, and then dump the tokens at a higher price making a big profits. If we wanted to analyze this kind of market movements we’ll need to gather data from somewhere. Fortunately exchanges provide REST and socket APIs for their market data, but mostly include 24h changes, and no method to retrieve smaller intervals such as 1 minute data, 5 minute, 30 minute or more. We’ll use [Bitfinex](https://docs.bitfinex.com/v1/reference) ticker data for this example. What is ticker data? A tick is a measure of the minimum upward or downward movement in the price of a security or it can also refer to the change in the price of a security from trade to trade. Example of a tick provided by Bitfinex: ``` { "mid":"244.755", (bid + ask) / 2 "bid":"244.75", Innermost bid "ask":"244.76", Innermost ask "last_price":"244.82", The price at which the last order executed "low":"244.2", Lowest trade price of the last 24 hours "high":"248.19", Highest trade price of the last 24 hours "volume":"7842.11542563", Trading volume of the last 24 hours "timestamp":"1444253422.348340958" The timestamp at which this information was valid } ``` As you can see the data provided by Bitfinex represents mostly 24h changes, highs and lows for the day, volume etc. We’re interested in the timestamp and last_price, which reflect the price of the asset at the current time. We’ll take all this data and store it in a MongoDB database. Our model looks like this: ``` var tickerSchema = new Schema({ bid: Number, bid_size: Number, ask: Number, ask_size: Number, daily_change: Number, daily_change_perc: Number, last_price: Number, volume: Number, high: Number, low: Number, created_at: { type: Date, required: true, default: Date.now }, symbol: String, exchange: String }); ``` Now that we have the data store we can go ahead and create our [Mongo Aggregation pipeline](https://docs.mongodb.com/manual/aggregation/) to extract the ticks in time intervals we need. ``` const pair = 'tETHUSD' const exchange = 'Bitfinex' const periods = 5; //time intervals to process data const minutesAgo = 30; let startDate = new Date() startDate.setMinutes(timeAgo.getMinutes()-minutesAgo) const endDate = new Date() const operations = [ { $match: { created_at: {$gte: startDate, $lt: endDate}, symbol: pair, exchange: exchange } }, { $group: { _id: { $add: [ { $subtract: [ { $subtract: [ "$created_at", new Date(0) ] }, { $mod: [ { $subtract: [ "$created_at", new Date(0) ] }, 1000 * 60 * period ]} ]}, new Date(0)] }, first_close: {$first: "$last_price"}, last_close: {$last: "$last_price"}, first_volume: {$first: "$volume"}, last_volume: {$last: "$volume"}, high: {$max: "$last_price"}, low: {$min: "$last_price"}, } }, { $project: { _id: 1, period_change: { $subtract: [ '$last_close', '$first_close' ] }, period_change_perc: (1 - ('$first_close' / '$last_close')), open: '$first_close', close: '$last_close', volume: { $subtract: [ '$last_volume', '$first_volume' ] }, high: '$high', low: '$low' } }, { $sort: { _id: 1 } } ]; Ticker.aggregate(operations,function(err, results) { }); ``` Our aggregation pipeline consists of 4 operations: 1. __$match__ : filters the tick data based on date, symbol and exchange 2. __$group__ : groups the data returned by the match operation into periods, in our case 5 minutes. Here we also compute additional value such as the new highs and lows for the perios, first close, last close, first volume, last volume. All this data can be extracted from the last close price and volume of the ticker. 3. __$project__ : the data resulted from the $group operation is passed into the $project phase, where we use [operators](https://docs.mongodb.com/manual/reference/operator/aggregation/) to compute the final output. For example period_change is *last_close*-*first_close* 4. __$sort__ : Make sure we sort the periods in ascending order Success! Output of our aggregation is an array of aggregated tickers at 5 minute intervals: ``` [ { _id: 2017-08-24T07:15:00.000Z, period_change: 0.2400000000000091, open: 319.2, close: 319.44, volume: -52.8511199200002, high: 319.44, low: 319.2 }, { _id: 2017-08-24T07:20:00.000Z, period_change: 0.07999999999998408, open: 319.44, close: 319.52, volume: 18.845093469994026, high: 319.52, low: 319.44 } ] ``` P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» References: **Aggregation - MongoDB Manual 3.4** [Aggregation operations process data records and return computed results.](https://docs.mongodb.com/manual/aggregation/) **General** [Current Version Bitfinex Websocket API version is 2.0](https://docs.bitfinex.com/v2/docs/ws-general) --- ### building-a-design-pattern-generator-for-cnc-laser-cutting.md I needed a front gate. Not just any gate β€” a metal gate with a perforated pattern that lets light through while keeping privacy. The kind of thing you see on modern architectural builds, where the sheet metal is laser-cut with a gradient of holes or lines that fade from dense to sparse. I called a few laser-cutting shops. Every single one asked the same thing: *"Can you send us the CAD file?"* I didn't have one. I had a mental image β€” something that gets denser at the bottom and fades toward the top, with rounded corners on the cutouts. Drawing that manually in CAD software, iterating on spacing and density, exporting to DXF, then realizing the proportions were off... that loop sounded like hours of pain. And I wanted to *see* the pattern before committing to a full sheet of metal. So I did what any developer with a deadline and a stubborn streak would do: I built a tool. --- ## The Problem: From Idea to Cut File Laser and CNC machines are incredibly precise, but they are dumb. They follow paths. The creative part β€” deciding where those paths go β€” is entirely on you. Most workflows look like this: 1. Sketch an idea on paper. 2. Open Illustrator / Inkscape / AutoCAD. 3. Draw shapes manually. 4. Copy, paste, nudge, guess at spacing. 5. Export to DXF or PDF. 6. Send to the shop. 7. Realize the scale is wrong. Go back to step 3. I wanted to collapse that loop into a single web page where I could tweak parameters and immediately see the result, measured in real millimeters, ready to export. --- ## What I Built The [Design Pattern Generator](/design-pattern-generator) is a standalone React app embedded in my Gatsby site. It runs entirely in the browser β€” no backend, no uploads, no accounts. Everything is local. ### Core Features **Live SVG preview** β€” Every change updates a vector preview in real time. Dimensions are exact: if you set the canvas to 1000 mm Γ— 2000 mm, the preview and the exported file match that precisely. **Four shape types** β€” Circles, squares, horizontal lines, and vertical lines. Lines are particularly useful for slatted designs where you want airflow or visibility control. Squares support rounded corners. Lines support variable length and corner radius. **Gradient density control** β€” This is where the tool gets interesting. You can set a global density (0–100%), but you can also apply a directional gradient: - Uniform - Left-to-right, right-to-left - Top-to-bottom, bottom-to-top - Radial (dense in the center, fading outward) The gradient uses a curved falloff so the fade feels natural, not mechanically linear. The sparse end of the gradient never drops to zero β€” it stays around 10% of the max density so the pattern doesn't completely vanish. **Floyd-Steinberg dithering** β€” To avoid the "grid of dots" look, the engine uses Floyd-Steinberg error diffusion when deciding whether to place a shape at each grid cell. This gives the pattern an organic, slightly irregular feel that works much better aesthetically than a strict probabilistic grid. **Coverage calculation** β€” The tool shows the exact percentage of material that will be removed. This is critical for structural decisions: if you're cutting a gate panel, you need to know how much strength you're losing. If you're designing a speaker grille, you need to know how much sound will pass through. **Export formats** β€” PDF for print and sharing, DXF for direct import into CAD/CAM software. Both are vector formats, so the shop gets clean paths at any scale. **Dark mode** β€” Because I built this at night, and staring at a white canvas hurts. --- ## How It Works The engine is a pure JavaScript function that takes a settings object and returns an array of shape descriptors. Each shape has a position, size, type, and optional properties like line length or corner radius. ```javascript // Simplified idea const shapes = generatePattern({ width: 1000, height: 2000, shapeType: 'circle', shapeSize: 20, spacing: 40, opacity: 30, gradientType: 'topToBottom', }); ``` The grid is computed from the canvas size, margins, shape size, and spacing. For each cell, the engine calculates a target density based on the gradient, applies the dithering algorithm, and if the cell passes, it places a shape. Lines are handled differently β€” they are placed in rows or columns with random lengths within a min/max range, skipping cells based on the same density logic. The SVG preview renders these shapes directly. The PDF exporter uses `svg2pdf.js` to convert the live SVG DOM into a vector PDF with exact page dimensions. The DXF exporter writes raw DXF entities (LWPOLYLINE for lines, CIRCLE for circles) so the file opens cleanly in AutoCAD, LibreCAD, or any CAM software. --- ## Why This Matters The real win isn't the code β€” it's the iteration speed. I can sit with my laptop, adjust the density, see the pattern change, and know immediately whether it will look right on a 2-meter gate. I can check the coverage percentage and make sure I'm not cutting away so much metal that the panel loses rigidity. I can export a DXF, email it to the shop, and have a quote the same day. Before writing this tool, I tried drawing patterns in Figma. It works, but it's tedious. Every change requires manual selection, nudging, and copy-pasting. The generator removes that friction entirely. The pattern is deterministic enough to look intentional, but random enough to feel organic. --- ## The Gate The gate I ended up with uses vertical lines, 30 mm thick, with a top-to-bottom density gradient. The lines are 100–400 mm long, placed in columns with random gaps. The top is sparse β€” almost open β€” letting light through. The bottom is dense, giving privacy and a solid visual anchor. The DXF went straight to the laser shop. The total cutout area is about 35% of the sheet. That felt right: enough transparency to keep the space from feeling like a wall, enough material to keep it strong. --- ## Try It You can use the generator here: [**/design-pattern-generator**](/design-pattern-generator) It's free, runs in your browser, and exports PDF and DXF. If you build something with it β€” a gate, a grille, a lamp shade, a room divider β€” I'd love to see it. --- ### building-semantic-search-over-a-knowledge-base.md Keyword search breaks whenever users and authors describe the same concept differently. A user searching *"how do I take money out of my account"* may never find an article titled *"Withdrawal Methods"*, even though it's exactly what they want. Semantic search fixes this by ranking documents based on meaning instead of token overlap. This post walks through a production semantic-search architecture for knowledge bases: retrieval, reranking, chunking, ingestion, caching, evaluation, and the operational tradeoffs that matter once you go past a prototype. --- ## Lexical, Semantic, and Hybrid Search Before going deeper, it's worth being precise about the three retrieval approaches you'll see compared in any serious search project, because they fail in different ways and the right choice depends on what your corpus and your users look like. **Lexical search** is the classical approach: tokenize the query and the documents, then score on token overlap with something like BM25. It's cheap, interpretable, requires no model, and is excellent at *exact-match* recall β€” product codes, error codes, version strings, proper nouns. If a user pastes `ERR_QUOTA_EXCEEDED` into the search box, lexical search will find it in milliseconds with no training data. Its failure mode is the one in the intro: when the user and the author describe the same concept with different words, token overlap goes to zero and recall collapses. *"Take money out"* and *"Withdrawal Methods"* share no tokens. **Semantic search** embeds query and documents into a shared vector space and retrieves by similarity, so paraphrase and synonymy are handled by construction. This is what the rest of this post is about. Its weakness is the mirror image of lexical's strength: rare tokens the embedding model never saw during training have nowhere meaningful to live in vector space, so they get smeared into nearby concepts. SKUs, internal API names, version numbers, and obscure proper nouns retrieve poorly even when an exact copy of the string exists in the corpus. **Hybrid search** runs both retrievers in parallel and fuses the results, usually with Reciprocal Rank Fusion (RRF) or a weighted blend of the two normalized score lists. You get exact-match recall on rare tokens *and* paraphrase recall on prose, at the cost of running two retrievers and tuning a fusion step. The selection rule, in practice: - **Pure lexical** β€” small or mostly-structured corpora, search-by-identifier workloads, environments where you don't want the operational weight of embeddings at all. - **Pure semantic** β€” prose-heavy knowledge bases, FAQ-style queries, traffic dominated by paraphrase. This is the assumption the rest of this post runs with. - **Hybrid** β€” mixed corpora with both prose and rare-token content: developer docs that include API names, e-commerce catalogs with SKUs, support content where users will happily paste error codes alongside natural-language questions. When you don't know in advance which mode your users will lean on, hybrid is the safe default. The rest of this post focuses on the semantic path because that's the harder system to get right β€” but the architecture below extends to hybrid cleanly: lexical retrieval becomes a second candidate source feeding the same reranker, and everything downstream stays the same. --- ## The Core Idea: Two-Stage Retrieval Modern semantic search almost universally uses two stages, because no single model is both fast enough to score every document *and* accurate enough to rank the top results well. ``` Query ──▢ Bi-encoder ──▢ Vector DB ──▢ Top 30 ──▢ Cross-encoder ──▢ Top N (fast, broad) (recall) (slow, precise) ``` **Stage 1 β€” Retrieval (bi-encoder).** A bi-encoder embeds the query and every document independently into the same vector space. At query time you only embed the query; document vectors are precomputed. You then ask a vector database for the *k* nearest neighbours by cosine similarity. This is fast because it's a single forward pass plus an ANN lookup. **Stage 2 β€” Reranking (cross-encoder).** A cross-encoder takes `(query, document)` pairs together and produces a relevance score. A bi-encoder compresses query and document independently into fixed vectors, so token-level nuance is lost; a cross-encoder attends over both texts jointly, which is what lets it learn that *"take money out"* and *"withdrawal"* are the same intent. The cost is that it can't be precomputed, and it's an order of magnitude slower per document β€” so you only run it over the top 30 candidates from stage 1. The split matters: you scale recall with the bi-encoder and precision with the cross-encoder, and you keep latency bounded. To make this concrete, take the query *"how do I take money out of my account"* through the pipeline: ``` Query: "how do I take money out of my account" Stage 1 β€” bi-encoder retrieves top candidates: 1. "Withdrawal Methods" 2. "Bank Transfer Limits" 3. "ATM Cash Withdrawal" 4. "Account Closure" ... (30 total) Stage 2 β€” cross-encoder reranks: 1. "Withdrawal Methods" (0.94) 2. "ATM Cash Withdrawal" (0.81) 3. "Bank Transfer Limits" (0.62) ... ``` The bi-encoder gets the right cluster of articles into the candidate set; the cross-encoder figures out that *"Withdrawal Methods"* is what the user actually wanted, even though *"Bank Transfer Limits"* shares more vocabulary with everyday banking language. ### Choosing models For most production workloads you don't need state-of-the-art research models. CPU-friendly small models are usually enough: - **Bi-encoder:** `intfloat/multilingual-e5-small` β€” 384 dimensions, multilingual, ~50–100ms per query on CPU. - **Cross-encoder:** `cross-encoder/ms-marco-TinyBERT-L2-v2` β€” small enough that scoring 30 candidates takes ~300–800ms on CPU. These run comfortably on commodity hardware. GPU inference is rarely justified at this scale: model size and batch sizes are small, CPU latency is well under your typical p99 budget, and CPU instances are dramatically cheaper. Always **L2-normalize** bi-encoder output. Cosine similarity on normalized vectors becomes a dot product, which most vector indexes optimize for, and it makes scores comparable across queries. --- ## Vector Storage: Why PostgreSQL + pgvector The reflexive choice is a dedicated vector database (Pinecone, Weaviate, Qdrant, Milvus). For a lot of systems that's overkill. If you already run PostgreSQL, the `pgvector` extension gives you: - Vectors as a column type alongside your normal relational data. - HNSW and IVFFlat indexes with cosine, L2, and inner-product distance. - ACID transactions, joins, filtering by `WHERE` clauses, real backups. - A boring, well-understood operational profile. A typical schema looks like this: ```sql CREATE EXTENSION vector; CREATE TABLE embeddings ( id BIGSERIAL PRIMARY KEY, article_id TEXT NOT NULL, tenant TEXT NOT NULL, language TEXT NOT NULL, chunk_type TEXT NOT NULL, -- 'title' | 'summary' | 'paragraph' text TEXT NOT NULL, embedding VECTOR(384) NOT NULL, metadata JSONB, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX ON embeddings USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); CREATE INDEX ON embeddings (tenant, language); ``` Multi-tenancy is best handled by a `tenant` column rather than separate databases. One HNSW index serves everyone, you filter at query time, and you don't have to manage N migration pipelines. The query at retrieval time: ```sql SELECT article_id, chunk_type, text, 1 - (embedding <=> $query_vector) AS similarity FROM embeddings WHERE tenant = $tenant AND language = $lang ORDER BY embedding <=> $query_vector LIMIT 30; ``` The `<=>` operator is cosine distance under `vector_cosine_ops`. Using it in `ORDER BY` with `LIMIT` is what triggers HNSW; selecting it as a column for the score does not. Dedicated vector databases become the right call when: - Vector counts reach tens or hundreds of millions. - Filtered ANN queries dominate and pgvector's planner struggles to combine HNSW with `WHERE` clauses efficiently. - You need multi-region replication of the index itself, not just the source data. - Search becomes its own platform with a dedicated team behind it. Before that point, pgvector is usually operationally simpler β€” one database, one backup story, one set of credentials. --- ## Chunking: The Underrated Decision A 2,000-word article embedded into a single vector is a bad vector. The signal from the introduction gets diluted by every paragraph that follows, and search recall drops on long-form content. The fix is to embed chunks. For a knowledge base, the natural chunks are: - **Title** β€” short, dense, very high signal for direct intent matches. - **Summary** β€” slightly longer, captures the "what is this about" framing. - **Paragraphs** β€” individual sections that may answer specific sub-questions. Each chunk becomes its own row in the embeddings table, all pointing back to the same `article_id`. At search time you retrieve chunks, then deduplicate to articles by taking the best-scoring chunk per article β€” weighted by chunk type so a title match counts for more than a paragraph match. Why max-with-weight rather than sum? Summing rewards long articles for having many paragraphs, which is exactly the opposite of what you want. The weight prevents a paragraph match from beating a title match on the same query, since title matches usually mean the article is *about* the query, not just *mentions* it. Reasonable starting weights are roughly 1.0 for titles, ~0.85 for summaries, and ~0.7 for paragraphs β€” tune from there against your evaluation set. A toy example makes the effect obvious. Query: *"reset MFA"*. ``` Article A β€” "Reset Multi-Factor Authentication" title similarity = 0.91 paragraph similarity = 0.74 Article B β€” "Account Security Best Practices" title similarity = 0.70 paragraph similarity = 0.82 (mentions MFA reset in passing) Weighted max: A = 0.91 Γ— 1.0 = 0.91 B = 0.82 Γ— 0.7 = 0.57 ``` Article A wins because the title strongly matches the intent, even though Article B has a higher raw paragraph similarity. Without weighting, B would beat A β€” and the user would land on the wrong article. For arbitrary text (long-form documentation, PDFs, scraped pages) you don't have natural title/summary/paragraph boundaries. There you need fixed-size chunking with a small overlap (~10–20%) so a concept that straddles a chunk boundary still appears in at least one chunk. For structured KB articles, the natural sections are almost always cleaner than fixed token windows β€” don't cargo-cult overlap into a domain that doesn't need it. --- ## Ingestion: Make It Asynchronous A naive ingestion endpoint embeds and writes synchronously inside the HTTP request. This works until: - An author bulk-uploads 5,000 articles and your API times out. - The embedding service goes briefly unhealthy and you lose writes. - A burst of updates pegs CPU and search latency goes through the roof. The fix is two-phase ingestion with a queue between them: ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” HTTP POST ──▢ β”‚ Ingestion API β”‚ ──▢ FIFO Queue ──▢ Worker pool β”‚ (validate + β”‚ (chunk β†’ embed β”‚ enqueue) β”‚ β†’ store β†’ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ invalidate cache) β”‚ HTTP 202 Accepted ``` **Phase A β€” synchronous.** Validate the payload (required fields, schema, size limits), enqueue a message, return `202 Accepted` with a tracking ID. This should take a few milliseconds. **Phase B β€” asynchronous.** A worker pool polls the queue, processes messages with long-polling and parallelism, and writes results to PostgreSQL. A FIFO queue (e.g. SQS FIFO) buys you ordering guarantees per message group β€” important for `CREATE` followed by `UPDATE` on the same article landing in the right order β€” plus a dead-letter queue for poison messages. Worker concurrency can scale on queue depth independently of HTTP traffic. Message actions worth supporting from day one: - `CREATE` β€” embed and insert chunks. - `UPDATE` β€” delete existing chunks for the article, then re-embed (it's simpler and safer than diffing). - `DELETE` β€” remove all chunks for an article. - `DELETE_ALL` β€” clear a tenant. Useful for re-indexing. --- ## Caching: Search-Result Cache, Not Embedding Cache The temptation is to cache embeddings. Don't bother β€” they're already in PostgreSQL, and recomputing them is rare. The high-value cache is at the **search-result** layer, where a popular query may be issued thousands of times per minute with the same exact answer. Cache-aside is the right pattern: 1. Search API receives a query, builds a cache key, checks Redis. 2. **Hit**: return immediately. Total latency is a single Redis round-trip. 3. **Miss**: run the full pipeline (embed β†’ retrieve β†’ rerank), store the result with a TTL, return. The cache key should encode every dimension that affects the result: ``` search:{tenant}:{lang}:{version}:{filter_hash}:{query_hash} ``` - `tenant`, `lang`, `version` are namespaces β€” they let you invalidate by prefix. - `filter_hash` covers any structured filters (categories, product IDs). - `query_hash` is a hash of the normalized query string (lowercased, trimmed, collapsed whitespace). A 5-minute TTL is a good starting point. Long enough to absorb traffic spikes from trending queries; short enough that stale results from edits self-heal even if invalidation fails. **Invalidation matters more than TTL.** When the ingestion worker writes a new article, it should delete cache entries that could now be wrong. The structured key prefix makes this a single pattern delete: ``` DEL search:{tenant}:* ``` You're trading freshness against efficiency. Per-tenant invalidation is usually the right granularity β€” finer-grained invalidation is hard to get right and rarely worth the complexity. --- ## Service Layout: Why Microservices Help Here The naive single-service deployment puts API handling, embedding, reranking, and ingestion in one process. This works on day one. It fails on day 100 because the workloads scale on completely different signals. A four-service split β€” search API, embedding service, reranker service, ingestion worker β€” lets each scale on what actually drives its load: | Service | Bottleneck | Scale on | |----------------|---------------------------|----------------| | Search API | HTTP/orchestration | Request rate | | Embedding | CPU (one inference/req) | CPU % | | Reranker | CPU (~30 inferences/req) | CPU % | | Ingestion | Backlog | Queue depth | The reranker is the interesting one. It receives 30 candidates *per single search* and runs the cross-encoder over all of them in one batch. That's roughly 6–10Γ— the CPU work of the embedding service per request, so under a 5Γ— traffic spike the reranker may need 8Γ— capacity while the embedding service only needs 2Γ—. If everything scaled together on a single signal, the reranker would be the bottleneck and your tail latencies would blow up. --- ## Evaluation: The System You Actually Tune Against This is the section that separates demos from production systems, and it's the one most teams skip until they regret it. Without an evaluation set, every change you make to the pipeline β€” a new model, a different chunking strategy, tweaked weights β€” becomes anecdotal. *"It feels better"* is not a metric. Build a labeled set early. It doesn't need to be huge; 50–200 `(query, relevant_article_ids)` pairs are enough to start catching regressions: | Query | Expected article | |------------------------|--------------------------| | "take money out" | `withdrawal-methods` | | "change my password" | `reset-password` | | "cancel subscription" | `billing-cancellation` | | "two factor reset" | `reset-mfa` | Then measure on every change: - **Recall@30** β€” did the relevant article appear in the top 30 candidates? This isolates the bi-encoder + index. - **MRR / nDCG@10** β€” was it ranked highly in the final result? This isolates the reranker and the score-aggregation logic. - **Latency p50/p95** β€” broken down by stage so you can see which subsystem is moving. - **Cache hit rate** β€” telling you whether your TTL and key design match real query patterns. The diagnostic value is what matters more than any individual number: - High Recall@30 but low MRR β†’ ranking problem. Look at the reranker, scoring weights, or chunk selection. - Low Recall@30 β†’ retrieval problem. Look at chunking, the bi-encoder, or filters that are too aggressive. - Both fine on the eval set but users complain β†’ your eval set doesn't represent real queries. Sample from logs. --- ## Putting Numbers on It A reasonable target for a system like this on commodity hardware: - **Cached search** (Redis hit): under 50ms end-to-end. - **Fresh search** (full pipeline): under 400ms p95. - Embedding: 50–100ms - Vector retrieval (HNSW over millions of rows): 20–50ms - Reranking 30 candidates: 300–500ms - **Ingestion**: 2–5 seconds per article end-to-end, fully async. A worker pool of 4–8 processes handles tens of thousands of articles per hour. These numbers are entirely CPU-only. The whole stack β€” Postgres, Redis, queue, four services on Kubernetes β€” runs comfortably for a few hundred dollars a month at moderate scale, which is far cheaper than most managed vector-DB offerings or GPU-backed inference setups. Worth being explicit: this architecture uses small transformer encoders (bi-encoder for retrieval, cross-encoder for reranking), not an LLM. There's no generative step in the request path, which is exactly why latency and cost stay flat. --- ## A Mental Model to Keep Semantic search at production scale is mostly about recognizing that the system has three axes β€” **recall, precision, freshness** β€” and each subsystem moves only one of them: - The bi-encoder and HNSW index drive **recall**. Get the right candidates into the top 30. - The cross-encoder drives **precision**. Order them correctly. - The ingestion pipeline and cache invalidation drive **freshness**. Show what's true now. Most search-quality problems are misdiagnosed because someone tries to fix a precision problem with a recall tool, or vice versa. If the right answer isn't in the top 30 candidates, no reranker can save you. If it's there but ranked 8th, no amount of bi-encoder tuning helps. Look at where in the pipeline the answer falls out, then fix that stage. The surprising part of running production semantic search is that the hard problems are rarely model problems. They're systems problems: chunking, evaluation, cache invalidation, ingestion ordering, latency under load, and knowing whether a given failure is about recall or ranking. --- ### cloning-windows-drive-from-mac-os-x-using-dd-disk-destroyer.md Decided to upgrade upgrade my Windows drive on my dual boot Hackintosh. Here's how to do it from your Mac OS X drive. Open your terminal. Run ```diskutil list``` to get the disk identifier you want to migrate. ```diskutil list``` In my case the Windows drive is ```/dev/disk1```. Let's clone the drive using ```dd```. Set input file to the drive you want to clone, make sure to add ```r```, makes it faster. Set output file to a ```.iso``` location of your choice. This operation will take a couple of hours. ```sudo dd if=/dev/rdisk1 of=/Users/mitzuuuu/Desktop/windows.iso``` Install your empty drive and run ```diskutil list``` again to get the identifier of the new drive, ```/dev/disk0``` in my case. Write the .iso you've previously generated to the new installed drive. This operation will take a couple of hours. ```sudo dd if=/Users/mitzuuuu/Desktop/windows.iso of=/dev/disk0``` After the migration is done, you need to take care of the ```Unnalocated space```. Boot into the Windows partition. Create a new partition, and finally merge it with the master windows partition if you want to. Success! --- ### how-i-grew-my-twitter-followers-from-500-to-1000-in-just-12-days.md ![twitter_stats](/images/blog/1_T1DMihbZ4Pjc7v6UdxvX7w.png) > Twitter can sometimes can make you feel like you’re tweeting to a lonesome abyss. Before we dive into the How To, let me tell you that I’m not a social media marketing expert. I’m a software engineer and #socialmedia doesn’t come natural to me πŸ˜…. My twitter followers have always stagnated at around 300–400. This is mostly because I was just consuming information and didn’t spend time sharing __relevant tweets__ and __building an audience__. Recently I’ve decided to do an experiment and see how far I can take my Twitter account. So let’s get down to it 🧐 My Twitter followers before doing this experiment: __435__ After doing some research I made a plan: 1. Setup profile bio, be authenthic, write what your passionate about. 2. Add a profile photo.. nobody wants to follow an β€œegghead” πŸ˜’ 3. Identify my audience. My audience revolves around #swift #objectivec #javascript #graphql #reactjs #nodejs πŸ‘πŸ». Use tools such as [hashtagify.me](https://hashtagify.me/) to research what hashtags to use. 4. Engage audience with relevant tweets. Be consistent! (post 3–6 times a day). Tweeting/retweeting too much will hurt your profile. 5. Follow and engage with __active__ people with the same interests as yourself. Search through relevant hasthtags (eg.: #technology) and __engage with the people that actively like and retweet other people’s posts__. 6. Engage in Twitter chats. This can lead to massive exposure. 7. PRO TIP: pin your most engaging tweets to your twitter profile. Use [Twitter Analytics](https://analytics.twitter.com/) to find your most popular tweets. In order to tweet to my twitter audience I’ve setup a workflow using the following tools: [Buffer](https://buffer.com/), [Feedly](https://feedly.com/) and [Zapier](https://zapier.com/). 1. [Feedly](https://feedly.com/) let’s you find and subscribe to quality publications. Also can easily be plugged into services such as [Zapier](https://zapier.com/) or [IFTTT](https://ifttt.com/). ![feedly_screen](/images/blog/1_wTj61rb3GZMKDcURL_aM5w.png) 2. [Zapier](https://zapier.com/) allows you to create jobs which check if a new article was published into a Feedly category and then automatically add it to my Buffer social media queue. ![zapier_screenshot](/images/blog/1_4r3hfl0rWW51jG6CnTsSpQ.png) 3. [Buffer](https://buffer.com/) is the last piece of the puzzle. The service schedules content to be sent out to my social media profile. TIP: Make sure to keep that queue filled and review it once every couple of days. __Add hashtags, mention authors for extra engagment__ πŸ‘Œ ![buffer_screenshot](/images/blog/1_aZjiPD1cuhn92DtDVU8Q3g.png) One takaway is that consitency is key… Building an audience takes time. Treat your Twitter profile like you were running a marathon, not a sprint. Let me know if you have any other tips for growing your social media presence πŸ™πŸ» See you on [Twitter](https://x.com/MihaiSerban) Happy *#GrowthHacking*! --- ### how-to-fix-broken-images-in-react.md ![Missing image example](/images/blog/1_xIlLqtM0dSTY3KZ03zckMg.png) In one of my recent projects we encountered many images which were missing from our S3 bucket. When I see something like this it just makes me sick 😫. `` provides us with two events, `onLoad` and `onError`. We can use these two keep track of the status of the image. `onError` is called when our image has failed to load, and we can set `src` to our preferred fallback image. `onLoad` is called when our image loaded successfully, nothing for us to do here. ``` import React from "react"; import PropTypes from "prop-types"; class Image extends React.Component { constructor(props) { super(props); this.state = { src: props.src }; } componentWillReceiveProps(nextProps) { if (this.props.src !== nextProps.src) { this.setState({ src: props.src }); } } handleImageLoad() { console.log("image loaded", this.state.src); } handleImageError() { console.log("image failed loaded", this.state.src); if (this.props.placeholder) { console.log("try load placeholder"); this.setState({ src: this.props.placeholder }); } } handleOnContextMenu(e) { console.log("handleOnContextMenu"); if (this.props.disableContextMenu) { e.preventDefault(); } } render() { const { src, placeholder, disableContextMenu, ...other } = this.props; // es7 return ( ); } } Image.defaultProps = { src: "", placeholder: "", disableContextMenu: false }; Image.propTypes = { src: PropTypes.string.isRequired, placeholder: PropTypes.string, disableContextMenu: PropTypes.bool }; export default Image; ``` Also available as a [Gist](https://gist.github.com/mihaiserban/751a84df361178db387e130d0c07693e). You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### how-to-handle-aws-ses-bounces-and-complaints.md If you’re thinking of implementing AWS Simple Email Service for your product, you might find out that you need a flow to handle email bounces and complaints before AWS approves your service quota increase and take your SES account out of sandbox mode. This requirement assures SES maintains a high reputation for only delivering mail people want and thereby maintaining a high deliverability for legitimate mail. What exactly are email bounces and complaints? **Bounce** email happens when an is returned to the sender because it cannot be delivered for some reason. **Complaints** are reports made by email recipients against emails they don’t want in their inbox. Mark as SPAM for example triggers such a report. Email Service Providers (ESPs), have what is called a β€œfeedback loop” with all of the major Internet Service Provides (ISPs). In case your API receives a Bounce/Complaint you should take steps to make sure it doesn’t happen again. Easiest way is to not send emails to that user, unless he agrees to receive it. #### Overview of the sendingΒ process The following figure shows the process of sending an email via [AWS SES](http://aws.amazon.com/ses/). ![AWS SES diagram](/images/blog/1_pEz2kdAeiT6ljb32F6J9fw.png) If the sender request to SES succeds then it can expect one of the following outcomes: * **success** * **bounce** * **complaint** #### Overview of handling of bounce/complaints The following figure shows the process of handling bounce/complaints by using [AWS SNS](https://aws.amazon.com/sns) service. ![AWS SES flow diagram](/images/blog/1_-eRHvZt-9R_R_7f6f0yCvw.png) Bounce and complaint notifications are available by email or through Amazon Simple Notification Service (Amazon SNS). By default, these notifications are sent to you via email by a feature called _email feedback forwarding_. #### 1. Setup AWS SNS topics for bounce and complaints Create the following topics in [AWS SNS](https://docs.aws.amazon.com/sns/latest/dg/sns-http-https-endpoint-as-subscriber.html#SendMessageToHttp.prepare): * ses-bounces-topic-prod * ses-complaints-topic-prod * ses-deliveries-topic-prod (optional) ![AWS SES create topic](/images/blog/1_JZ8CVlRWquIv20SKnUIjHg.png) After creating each topic, you’ll receive a identity id is called **ARN,** which we need in the next step of creating a SNS subscription. ![AWS SES topics](/images/blog/1_sWjh8Qxn-o5wyqvI5Ipe3Q.png) Head to **SNS Subscriptions** and create a SNS subscription for the bounce and complaint topics you’ve previously created. This is where we need to specify a Endpoint where we’ll receive notifications from each topic. Endpoint must be a **POST** method on your backend. ![AWS SES create subscription](/images/blog/1__Bnw9nIcRwC4LjH9hd3Mow.png) Each Subscription needs to be confirmed, after creation they are in a **PendingConfirmation** state. To confirm our subscription, we need to implement the endpoints in our backend, and call Request confirmations from the SNS dashboard. In the body received on our server we’ll find the SubscribeURL or Token which we can use to confirm. Call sns.confirmSubscription() with the Token or copy pasting SubscribeURL into SNS Dashboard. ![AWS SES confirm subscription url](/images/blog/1_T_bSIOjMnaOaHs9PwEevnw.png) > **I’ve provided the code to subscribe and confirm each endpoint on** [**Github**](https://gist.github.com/mihaiserban/8a03fd28e54cac8856dbdfebd95bd7b3)**.** > **TIP: Make sure your IAM User has access to SNS** #### 2. Configure SES to publish notifications to each created SNSΒ topic Go to the SES Managment Console -> Email Addresses -> Select email address, and open Notifications. Edit configuration and select the SNS topic for each type of notification. ![AWS SES notifications configuration](/images/blog/1_k4fHq6CgGYUeXZjNmyXOLA.png) #### 3. Testing using [AWS Mailbox Simulator](https://aws.amazon.com/blogs/aws/mailbox-simulator-for-the-amazon-simple-email-service/) The AWS mailbox simulator can be found in SES Managment Console and provides a way to test the way your implementation handles scenarios like bounces and complaints. ![AWS SES test email](/images/blog/1_Pydm3yb5aGuuRerVw6mxcQ.png) Mail sent to **success@simulator.amazonses.com** will be treated as delivered successfully. Mail sent to **bounce@simulator.amazonses.com** will be rejected with an SMTP 550 (β€œUnknown User”) response code. Amazon SES will send you a bounce notification by email or by SNS notification. Mail sent to **ooto@simulator.amazonses.com** will be treated as delivered successfully. Mail sent to **complaint@simulator.amazonses.com** will simulate the case in which the recipient clicks **Mark as Spam** within their email application and the ISP sends a complaint response to Amazon SES. Mail sent to **blacklist@simulator.amazonses.com** will cause Amazon SES to block the send attempt and return a [MessageRejected](http://docs.amazonwebservices.com/ses/latest/DeveloperGuide/Troubleshooting.MessageRejected.html) error containing an β€œAddress Blacklisted” error message. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-arrow-functions.md ![ES6 Arrow functions](/images/blog/1_zxHFGY9JpcDDsB5vAcNMtg.png) Arrows are a function shorthand using the `=>` syntax. Arrow functions allow you to preserve the lexical value of `this`. Take the example below where we have a nested function, in which we would like to preserve the context of `this` from its lexical scope: ``` function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(function (character) { return this.name + character; // Cannot read property 'name' of undefined }); }; ``` Using Arrow Functions, the lexical value of `this` isn't shadowed and we can re-write the above as shown: ``` function Person(name) { this.name = name; } Person.prototype.prefixName = function (arr) { return arr.map(character => this.name + character); }; ``` If an arrow is inside another function, it shares the `arguments` variable of its parent function. Example: ``` // Lexical arguments function square() { let example = () => { let numbers = []; for (let number of arguments) { numbers.push(number * number); } return numbers; }; return example(); } square(2, 4, 7.5, 8, 11.5, 21); // returns: [4, 16, 56.25, 64, 132.25, 441] ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-async-await.md ![ES6 Async/Await](/images/blog/1_U5MZoXlTOdyxlwf7XBqcHw.png) * Async/await is a new way to write asynchronous code. Previous options for asynchronous code are callbacks and promises. * Async/await is built on top of promises. It cannot be used with plain callbacks or node callbacks. * Async/await makes asynchronous code look and behave a little more like synchronous code. Note that `await` may only be used in functions marked with the `async` keyword. It works similarly to generators, suspending execution in your context until the promise settles. If the awaited expression isn’t a promise, its casted into a promise. `async await` allows us to perform the same thing we accomplished using Generators and Promises with less effort: ``` var request = require('request'); function getJSON(url) { return new Promise(function(resolve, reject) { request(url, function(error, response, body) { resolve(body); }); }); } async function main() { var data = await getJSON(); console.log(data); } main(); ``` Under the hood, it performs similarly to [Generators](https://medium.com/@serbanmihai/javascript-es6-cheatsheet-generators-997cc977f7f1). You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-classes.md ![ES6 Classes](/images/blog/1_zdv7_BKFCrpmGwfPgu6j3A.png) Prior to ES6, we implemented Classes by creating a constructor function and adding properties by extending the prototype: ``` function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } Person.prototype.incrementAge = function () { return this.age += 1; }; ``` And created extended classes by the following: ``` function Personal(name, age, gender, occupation, hobby) { Person.call(this, name, age, gender); this.occupation = occupation; this.hobby = hobby; } Personal.prototype = Object.create(Person.prototype); Personal.prototype.constructor = Personal; Personal.prototype.incrementAge = function () { Person.prototype.incrementAge.call(this); this.age += 20; console.log(this.age); }; ``` ES6 classes are a simple sugar over the prototype-based OO pattern. Classes support prototype-based inheritance, super calls, instance and static methods and constructors: ``` class Person { constructor(name, age, gender) { this.name = name; this.age = age; this.gender = gender; } incrementAge() { this.age += 1; } } ``` And extend them using the `extends` keyword: ``` class Personal extends Person { constructor(name, age, gender, occupation, hobby) { super(name, age, gender); this.occupation = occupation; this.hobby = hobby; } incrementAge() { super.incrementAge(); this.age += 20; console.log(this.age); } } ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-destructuring.md ![ES6 Destructuring](/images/blog/1_YujTHdJ1Hx9AWIe0CalxPg.png) ### Destructuring Destructuring is a convenient way of extracting multiple values from data stored in (possibly nested) objects and Arrays. ### Array Destructuring assignment allows you to assign the properties of an array using syntax that looks similar to array literals. Old way: ``` var first = someArray[0]; var second = someArray[1]; var third = someArray[2]; ``` New way: ``` let [first, second, third] = someArray; ``` If you want to declare your variables at the same time, you can add a `var`, `let`, or `const` in front of the assignment. ``` var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array; ``` We can even skip a few variables: ``` let [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz" ``` There’s also no need to match the full array: ``` let array = [1, 2, 3, 4]; let [a, b, c] = array; console.log(a, b, c) // -------- 1 2 3 ``` You can capture all trailing items in an array with a β€œrest” pattern: ``` const array = [1, 2, 3, 4]; const [head, ...tail] = array; console.log(head); // 1 console.log(tail); // [2, 3, 4] ``` Rest parameter must be applied as the last element, otherwise you’ll get a `SyntaxError`. ``` let array = [1, 2, 3, 4]; let [...head, d] = array; // Uncaught SyntaxError: Unexpected token... ``` ### Object Old way of destructuring an object: ``` var person = { first_name: 'Joe', last_name: 'Appleseed' }; var first_name = person.first_name; // 'Joe' var last_name = person.last_name; // 'Appleseed' ``` New way of destructuring an object: ``` let person = { first_name: 'Joe', last_name: 'Appleseed' }; let {first_name, last_name} = person; console.log(first_name); // 'Joe' console.log(last_name); // 'Appleseed' ``` When you destructure on properties that are not defined, you get undefined: ``` let { missing } = {}; console.log(missing); // undefined ``` You can also destructure in a for-of loop: ``` const arr = ['a', 'b']; for (const [index, element] of arr.entries()) { console.log(index, element); } // Output: // 0 a // 1 b ``` We can use rest operator on an object as well (ES7): ``` let object = { a: 'A', b: 'B', c: 'C', d: 'D', } const { a, b, ...other } = object; // es7 console.log(other); // {c: 'C', d: 'D'} ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-generators.md ![ES6 generators](/images/blog/1_rwj0mwY2iJ391EP3EVb0PA.png) A `generator` is a function which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. Generators in JavaScript are a very powerful tool for asynchronous programming as they mitigate the problems with callbacks, such as `Callback Hell` and `Inversion of Control`. This pattern is what `async` functions are built on top of. For creating a generator function, we use `function *` syntax instead of just `function`. Calling a generator function does not execute its body immediately; an iterator object for the function is returned instead. When the iterator’s `next()` method is called, the generator function's body is executed until the first `yield` expression, which specifies the value to be returned from the iterator or, with `yield*`, delegates to another generator function. The `next()` method returns an object with a value property containing the yielded value and a done property which indicates whether the generator has yielded its last value as a boolean. Calling the `next()` method with an argument will resume the generator function execution, replacing the yield expression where execution was paused with the argument from `next()`. **Simple example:** ``` function* generator(i) { yield i; yield i + 10; } var gen = generator(10); console.log(gen.next().value);// expected output: 10 console.log(gen.next().value); // expected output: 20 ``` **Example with yield\*:** ``` function* anotherGenerator(i) { yield i + 1; yield i + 2; yield i + 3; } function* generator(i) { yield i; yield* anotherGenerator(i); yield i + 10; } var gen = generator(10); console.log(gen.next().value); // 10 console.log(gen.next().value); // 11 console.log(gen.next().value); // 12 console.log(gen.next().value); // 13 console.log(gen.next().value); // 20 ``` **Infinite Data generator example:** ``` function * naturalNumbers() { let num = 1; while (true) { yield num; num = num + 1 } } const numbers = naturalNumbers(); console.log(numbers.next().value) // 1 console.log(numbers.next().value) // 2 ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-getter-and-setter-functions.md ![ES6 getter and setter functions](/images/blog/1_rt1b55HKcfIWZQf-olDhbQ.png) ES6 has started supporting getter and setter functions within classes. Using the following example: ``` class Person { constructor(name) { this._name = name; } get name() { if(this._name) { return this._name.toUpperCase(); } else { return undefined; } } set name(newName) { if (newName == this._name) { console.log('I already have this name.'); } else if (newName) { this._name = newName; } else { return false; } } } let person = new Person("John Doe"); // uses the get method in the background if (person.name) { console.log(person.name); // John Doe } // uses the setter in the background person.name = "Jane Doe"; console.log(person.name); // Jane Doe ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-helpful-array-functions.md ![ES6 array functions](/images/blog/1__1bFnrLKJfM9oDP_twk8Pg.png) `**from**` ``` const inventory = [ {name: 'mars', quantity: 2}, {name: 'snickers', quantity: 3} ]; console.log(Array.from(inventory, item => item.quantity + 2)); // [4, 5] ``` `**of**` ``` Array.of("Twinkle", "Little", "Star"); // returns ["Twinkle", "Little", "Star"] ``` `**find**` ``` const inventory = [ {name: 'mars', quantity: 2}, {name: 'snickers', quantity: 3} ]; console.log(inventory.find(item => item.name === 'mars')); // {name: 'mars', quantity: 2} ``` `**findIndex**` ``` const inventory = [ {name: 'mars', quantity: 2}, {name: 'snickers', quantity: 3} ]; console.log(inventory.findIndex(item => item.name === 'mars')); // 0 ``` `**fill**` method takes up to three arguments value, start and end. The start and end arguments are optional with default values of 0 and the length of the this object. ``` [1, 2, 3].fill(1); // [1, 1, 1] [1, 2, 3].fill(4, 1, 2); // [1, 4, 3] ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-helpful-string-functions.md ![ES6 string functions](/images/blog/1_1FqOzjfkPr33etNp8oqYew.png) ### .includes( ) ``` var string = 'string'; var substring = 'str'; console.log(string.indexOf(substring) > -1); ``` Instead of checking for a return value `> -1` to denote string containment, we can simply useΒ `.includes()` which will return a boolean: ``` const string = 'string'; const substring = 'str'; console.log(string.includes(substring)); // true ``` ### .repeat( ) ``` function repeat(string, count) { var strings = []; while(strings.length < count) { strings.push(string); } return strings.join(''); } ``` In ES6, we now have access to a nicer implementation: ``` // String.repeat(numberOfRepetitions) 'str'.repeat(3); // 'strstrstr' ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-map-weakmap.md ![ES6 Map snippet](/images/blog/1_nK_beIXw-lIf1wTpd2kocg.png) ### Map A `Map` is a data structure allows to associate data to a key. Before it’s intruduction in ES6, people generally used objects as maps, by associating some object or value to a specific key value: ``` const person = {} person.name = 'John' person.age = 18 console.log(person.name) //John console.log(person.age) //18 ``` `Map` example: ``` const person = new Map() person.set('name', 'John') person.set('age', 18) const name = person.get('name') const age = person.get('age') console.log(name) //John console.log(age) //18 ``` The `Map` also provide us with methods to help us manage the data. `delete()` method - deletes an item from a map by key: ``` person.delete('name') ``` `clear()` method - delete all items from a map: ``` person.clear() ``` `has()` method - check if a map contains an item by key: ``` const hasName = person.has('name') ``` `size()` method - check the number of items in a map: ``` const size = person.size ``` We can also use a couple of methods to iterate: `entries()`β€Šβ€”β€Šget all entries `keys()`β€Šβ€”β€Šget only all keys `values()`β€Šβ€”β€Šget only all values Find more details about `Map` [**here**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) ### WeakMap A `WeakMap` is a special kind of map. In a `Map`, items are never garbage collected. A `WeakMap` instead lets all its items be freely garbage collected. Every key of a `WeakMap` is an object. When the reference to this object is lost, the value can be garbage collected. Main differences between `WeakMap` and `Map`: * you cannot iterate over the keys or values (or key-values) of a WeakMap * you cannot clear all items from a WeakMap * you cannot check its size A WeakMap exposes those methods, which are equivalent to the Map ones: ``` get(k) set(k, v) has(k) delete(k) ``` The use cases of a `WeakMap` are less evident than the ones of a `Map`, and you might never find the need for them, but essentially it can be used to build a memory-sensitive cache that is not going to interfere with garbage collection, or for careful encapsualtion and information hiding. Find more details about `WeakMap` [**here**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap). You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-modules.md ![ES6 modules](/images/blog/1_AVuC2PTg3VOE6EEZTfnydw.png) Prior to ES6, we used libraries such as [Browserify](http://browserify.org/) to create modules on the client-side, and [require](https://nodejs.org/api/modules.html#modules_module_require_id) in **Node.js**. With ES6, we can now directly use modules of all types (AMD and CommonJS). ### Exporting inΒ CommonJS ``` module.exports = 1; module.exports = { foo: 'bar' }; module.exports = ['foo', 'bar']; module.exports = function bar () {}; ``` ### Exporting inΒ ES6 **Named Exports:** ``` export function multiply (x, y) { return x * y; }; ``` As well as **exporting a list** of objects: ``` function add (x, y) { return x + y; }; function multiply (x, y) { return x * y; }; export { add, multiply }; ``` **Default export:** In our module, we can have many named exports, but we can also have a default export. It’s because our module could be a large library and with default export we can import then an entire module. Important to note that there’s only `one default export per module`. ``` export default function (x, y) { return x * y; }; ``` This time we don’t have to use curly braces for importing and we have a chance to name imported statement as we wish. ``` import multiply from 'module'; // === OR === import whatever from 'module'; ``` A module can have both named exports and a default export: ``` // module.js export function add (x, y) { return x + y; }; export default function (x, y) { return x * y; }; // app.js import multiply, { add } from 'module'; ``` The default export is just a named export with the special name default. ``` // module.js export default function (x, y) { return x * y; }; // app.js import { default } from 'module'; ``` ### Importing inΒ ES6 ``` import { add } from 'module'; ``` We can even import many statements: ``` import { add, multiply } from 'module'; ``` Imports may also be **aliased**: ``` import { add as addition, multiply as multiplication } from 'module'; ``` and use wildcard (`*`) to import all exported statemets: ``` import * from 'module'; ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-promises.md ![ES6 Promises](/images/blog/1_AqkCUN-kD_fLefEFPnX2Uw.png) Promises are one of the most exciting additions to JavaScript ES6. Promises are a pattern that greatly simplifies asynchronous programming by making the code look synchronous and avoid problems associated with callbacks. Prior to ES6, we used [bluebird](https://github.com/petkaantonov/bluebird) or [Q](https://github.com/kriskowal/q). Now we have Promises natively. `A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.` The `resolve` and `reject` are functions themselves and are used to send back values to the promise object. ``` const myPromise = new Promise((resolve, reject) => { if (Math.random() * 100 <= 90) { resolve('Hello, Promises!'); } reject(new Error('In 10% of the cases, I fail. Miserably.')); }); myPromise.then((resolvedValue) => { console.log(resolvedValue); //Hello, Promises! }, (error) => { console.log(error); //In 10% of the cases, I fail. Miserably. }); ``` **Chaining Promises:** Promises allow us to turn our horizontal code (callback hell): ``` func1(function (value1) { func2(value1, function (value2) { func3(value2, function (value3) { // Do something with value 3 }); }); }); ``` Into vertical code like so: ``` func1(value1) .then(func2) .then(func3, value3 => { // Do something with value 3 }); ``` **Parallelize Promises:** We can use `Promise.all()` to handle an array of asynchronous operations. ``` let urls = [ '/api/commits', '/api/issues/opened', '/api/issues/assigned', '/api/issues/completed', '/api/issues/comments', '/api/pullrequests' ]; let promises = urls.map((url) => { return new Promise((resolve, reject) => { $.ajax({ url: url }) .done((data) => { resolve(data); }); }); }); Promise.all(promises) .then((results) => { // Do something with results of all our promises }); ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-set-weakset.md ![ES6 Set](/images/blog/1_AJDn2sPnvaDVOHZo2F3Zaw.png) A `Set` is a collection for unique values. The values can be primitives or object references. ``` let set = new Set(); set.add(1); set.add('1'); set.add({ key: 'value' }); console.log(set); // Set {1, '1', Object {key: 'value'}} ``` Most importantly is that it does not allow duplicate values, one good use if to remove duplicate values from an array: ``` [ ...new Set([1, 2, 3, 1, 2, 3]) ] //[1, 2, 3] ``` Iteration using built-in method forEach and for..of: ``` // forEach let set = new Set([1, '1', { key: 'value' }]); set.forEach(function (value) { console.log(value); // 1 // '1' // Object {key: 'value'} }); // for..of let set = new Set([1, '1', { key: 'value' }]); for (let value of set) { console.log(value); // 1 // '1' // Object {key: 'value'} }; ``` Similar to `Map`, `Set` provides us with methods such as `has()`, `delete()`, `clear()`. Find more details about `Set` [**here**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) ### WeakSet Like a `WeakMap`, `WeakSet` is a `Set` that doesn’t prevent its values from being garbage-collected. It has simpler API than `WeakMap`, because has only three methods: ``` new WeakSet([iterable]) WeakSet.prototype.add(value) : any WeakSet.prototype.has(value) : boolean WeakSet.prototype.delete(value) : boolean ``` Important thing to note `WeakSet` is a collection that canβ€˜t be iterated and whose size cannot be determined. Find more details about `WeakSet` [**here**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-spread-operator.md ![ES6 Spread Operator](/images/blog/1_rA3qD8SBiE_BrmeWuufvYw.png) ### Spread Operator The spread syntax is simply three dots:Β `...` It allows an iterable to expand in places where 0+ arguments are expected. ### Calling Functions withoutΒ Apply: ``` function doStuff (x, y, z) { } var args = [0, 1, 2]; // Call the function, passing args doStuff.apply(null, args); doStuff(...args); ``` Using spread operator: ``` const arr = [2, 4, 8, 6, 0]; const max = Math.max(...arr); console.log(max); //8 ``` Or another example using Math functions: ``` let mid = [3, 4]; let arr = [1, 2, ...mid, 5, 6]; //[1, 2, 3, 4, 5, 6] ``` ### Combine arrays ``` let arr = [1,2,3]; let arr2 = [...arr]; // like arr.slice() arr2.push(4) ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-string-templates.md ![string_templates](/images/blog/1_OxzGYSWzbivvvMcTkbjC9w.png) Template Strings use back-ticks (\`\`) rather than the single or double quotes we’re used to with regular strings. A template string could thus be written as follows: ``` const greeting = `Yo World!`; ``` **String Substitution**: Substitution allows us to place any valid JavaScript expression inside a Template Literal, the result will be output as part of the same string. Template Strings can contain placeholders for string substitution using the ${ } syntax: ``` var name = "Brendan"; console.log(`Yo, ${name}!`); //"Yo, Brendan!" ``` We can use expression interpolation to embed for some readable inline math: ``` var a = 10; var b = 10; console.log(`${a+b}`); //20 ``` They are also very useful for functions inside expressions: ``` function fn() { return "inside fn"; } console.log(`outside, ${fn()}, outside`); // outside, inside fn, outside. ``` __Multiline Strings:__ Multiline strings in JavaScript have required hacky workarounds for some time. Template Strings significantly simplify multiline strings. Simply include newlines where they are needed and BOOM. ``` let text = `In ES5 this is not legal.` ``` __Unescaped template strings:__ We can now construct strings that have special characters in them without needing to escape them explicitly. ``` var text = "This string contains "double quotes" which are escaped."; let text = `This string contains "double quotes" which don't need to be escaped anymore.`; ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### javascript-es6-cheatsheet-variable-declarations.md ![es6-variables](/images/blog/1_T6qcNaaF8T0HedbwF2ZmyQ.png) ES6 brought `let` and `const` with proper lexical scoping. `let` is the new `var`. Constants work just like `let`, but can’t be reassigned. `let` and `const` are block scoped. Therefore, referencing block-scoped identifiers before they are defined will produce a `ReferenceError`. Example using `var`: ``` var variable = 5; { console.log('inside', variable); //5 var variable = 10; } console.log('outside', variable); //10 ``` Example using `const`: ``` const variable = 5; variable = variable*2; // TypeError: Attempted to assign to readonly property. ``` Constants are tricky with array and objects. The `reference` becomes constant but the value does not. ``` const variable = [5]; console.log(variable) // [5] variable = [2]; //TypeError: Attempted to assign to readonly property. variable[0] = 1; console.log(variable) // [1] ``` You can find a more complete ES6 cheetsheet on my [Github](https://github.com/mihaiserban/es6-cheetsheet/blob/master/README.md) page. P.S. If you ❀️ this, make sure to follow me on [Twitter](https://x.com/MihaiSerban), and share this with your friends πŸ˜€πŸ™πŸ» --- ### neural-llm-router-coding-assistant.md I run an AI gateway that sits between my coding agents and a pool of LLM providers. The gateway handles health checks, failover, and model resolution. But it had one blind spot: it never looked at the *content* of what I was asking. Every request was routed based on whatever model the agent picked β€” opencode would say "I think this is a coding task, use `coder`" and the gateway would oblige. That works, but it wastes money. An exploration task ("search the codebase for User model references") doesn't need a reasoning-heavy $0.56/M output model. A `deepseek-v4-flash` at 20x cheaper would do fine. So I built a neural router that reads the *first user message* and classifies it into one of four task types: **explore**, **plan**, **build**, or **quick**. Based on the task type, the gateway picks the right model pool and reasoning effort. The classifier: a 5,120-parameter linear layer on top of Qwen-0.6B. --- ## The architecture The router is a sidecar service. The gateway calls it over HTTP, falls back to the default model if it's unreachable, and opens a circuit breaker after the first failure. ```text openCode/Codex β†’ gateway :4100 β†’ router sidecar :5560 β†’ Qwen3-0.6B β†’ head β†’ {task,reasoning} β”‚ └─ (fallback) config.default_model ``` The head is borrowed from [TRINITY](https://arxiv.org/abs/2512.04695) (Xu et al., ICLR 2026): a single weight matrix `W ∈ R^{7Γ—1024}` β€” no bias, no activation. Seven logits split into two softmax groups: | Group | Dimensions | Outputs | |---|---|---| | Task type | 4 logits | explore, plan, build, quick | | Reasoning effort | 3 logits | low, medium, high | At inference, the encoder runs the user message through Qwen3-0.6B, extracts a 1024-dimensional hidden state from the final layer (mean-pooled across all tokens, L2-normalized), and multiplies by the head. That's it. The output maps 1-to-1 into the gateway's routing config: ```yaml task_to_combo: explore: explorer # fast/cheap models (deepseek-v4-flash) plan: planner # reasoning models (glm-5.2, deepseek-v4-pro) build: coder # powerful coding models (kimi-k2.7-code) quick: coder-fast # cheap models for commits, bash commands task_to_reasoning: explore: low plan: high build: high quick: low ``` --- ## Training data came from my own sessions I didn't have to collect new data. opencode stores every session in a SQLite database at `~/.local/share/opencode/opencode.db` with 18,000+ messages across 500 sessions. Each session has an `agent` field β€” the task type opencode already inferred: ```sql SELECT s.agent, s.model, p.data FROM session s JOIN message m ON m.session_id = s.id JOIN part p ON p.message_id = m.id WHERE s.agent IN ('build', 'plan', 'explore', 'librarian', 'general') AND json_extract(m.data, '$.role') = 'user' AND json_extract(p.data, '$.type') = 'text' ``` The `agent` field was my label. I mapped it: | opencode agent | Task type | Sessions | |---|---|---| | explore, librarian | explore | 53 | | plan | plan | 192 | | build | build | 383 | | build (commit, bash, git, etc.) | quick | 181 | The `quick` split is heuristic β€” any build message containing "commit", "git push", "bash", "run", etc. got labeled quick instead. 809 labeled samples total. --- ## Training: from 47% to 79% First pass was disappointing. Penultimate-token encoding + SGD got stuck at 47.5% task accuracy. The hidden states for different task types were almost indistinguishable β€” all coding prompts look similar under cosine similarity (0.3–0.5 within-class and between-class alike). Two changes fixed it: **Mean pooling instead of penultimate token.** The penultimate token only captures the last word's context. Mean-pooling across all tokens carries the full query semantics. The paper uses penultimate for multi-turn transcripts where the last word is the role signal β€” but for single-message classification, mean pooling works better. **Adam with class-weighted loss.** The build class dominated (383 samples vs. 53 explore). Without weights, the head leaned toward build. With inverse-frequency class weights and Adam at lr=0.001, convergence was smooth: | Epoch | Task accuracy | Reasoning accuracy | Loss | |---|---|---|---| | 20 | 67.3% | 86.4% | 1.47 | | 100 | 73.3% | 88.9% | 0.92 | | 200 | 78.6% | 91.2% | 0.73 | The reasoning accuracy is high because the labels are deterministic (taskβ†’reasoning mapping), but the head still learns meaningful signal from the message content. --- ## Inference on Apple Silicon Qwen3-0.6B runs on MPS. The entire stack β€” encoder + head β€” is a single Python process with FastAPI: ```python @app.post("/route") async def route(req: RouteRequest) -> RouteResponse: h = encoder.encode_mean_pool(req.transcript) task_type, reasoning, debug = head.select(torch.as_tensor(h)) return RouteResponse(task_type=task_type.value, reasoning_effort=reasoning.value, ...) ``` Latency on an M3 Max: **96ms warm, 624ms cold start**. The head weights file is 30KB. Sample predictions: ``` "Search the codebase for User model" β†’ explore (70.4%) low (51.0%) "Write a function to parse markdown" β†’ build (68.5%) high (75.8%) "Commit changes with fix message" β†’ quick (66.2%) high (51.0%) "Design a real-time chat architecture"β†’ build (40.3%) high (88.2%) ``` The plan/build boundary is the hardest β€” "design system architecture" and "write a function" both look like structured tasks with similar vocabulary. The head leans toward build for anything code-related, which is the safe default since a coding task can't be ruined by too much capability. --- ## What's next **Deployment-level optimization.** Right now the router picks a combo bucket. The next step is per-deployment scoring within a combo β€” the cheapest available provider that's healthy and close to the latency target gets the request. This is where CMA-ES (the TRINITY paper's training algorithm) comes in, optimizing for `quality - Ξ» Γ— cost`. **Self-improving C-A-F loop.** The [Agent-as-a-Router](https://arxiv.org/abs/2606.22902) paper (Zhou et al., 2026) describes a Context-Action-Feedback loop where the router learns from every outcome. My gateway already logs usage events to Postgres β€” token counts, latency, served deployment, cache hits. Adding a Verifier (execution success) and a Memory module (vector store of past routing decisions) turns the router from a static classifier into an online learner. **Avoiding routing collapse.** The [When Routing Collapses](https://arxiv.org/abs/2602.03478) paper (Lai & Ye, 2026) shows a pervasive failure mode: routers default to the most expensive model as the cost budget increases, even when cheaper models suffice. Their fix β€” learning rankings instead of scalar scores β€” is something I'll adopt when moving to deployment-level routing. --- ## Bottom line A 5,120-parameter linear layer trained on my own 809 coding sessions classifies requests with enough accuracy to route cheap models to exploration tasks. The entire router runs locally on Apple Silicon in under 100ms. If the router dies, the gateway doesn't care β€” it just falls back to the safe default. --- ### replacing-orchestrator-agents-with-governance-worker-synthesis.md My coding agents use a pattern I suspect many of you recognize: an orchestrator agent plans the work, spawns minion sub-agents, collects their results, and synthesizes the final answer. It works fine for short tasks. But on anything long-horizon β€” a multi-file refactor, a cross-repo analysis, or a research sprint β€” it falls apart. The orchestrator's context window becomes the hard ceiling. Every minion result gets dumped back into the same window. By the third or fourth sub-task, the orchestrator is drowning in accumulated context and accuracy collapses. The pattern is fundamentally flawed. I decided to tear it out and replace it. But first, I wanted evidence β€” not vibes. Here's what 11 arXiv papers told me. ## The evidence against orchestrators **[ClawArena-Team](https://arxiv.org/abs/2606.31174)** (Jun 2026) built a benchmark specifically to measure a single LLM's ability to manage sub-agents. Their finding: **no model exceeds 50% workspace-permission precision.** The bottleneck isn't model capability β€” it's the architecture of delegating through a single constrained context window. Cost and management quality were essentially decoupled: API spend varied over 100x while management scores clustered in a tight 9.9-point band. **[ExtAgents](https://arxiv.org/abs/2505.21471)** (ACL 2026) identified two core bottlenecks in existing agent orchestration designs and showed that distributing knowledge input across multiple agents in parallel β€” rather than funneling everything through one orchestrator β€” significantly outperforms single-context approaches, regardless of whether the input fits in the context window. **[OrgAgent](https://arxiv.org/abs/2604.01020)** (Apr 2026) compared flat multi-agent systems against hierarchical ones organized like a company β€” governance, execution, and compliance layers. The hierarchical structure improved performance by **102.73%** while reducing token usage by **74.52%** on SQuAD 2.0. The key insight: governance should plan and allocate, not micromanage execution. **[ReAcTree](https://arxiv.org/abs/2511.02424)** (AAMAS 2026) attacked the long-horizon problem directly. Monolithic ReAct-style trajectories entangle all past decisions and observations; ReAcTree decomposes complex goals into a dynamically constructed agent tree where each node handles a subgoal independently. On WAH-NL, it achieved **61% goal success rate vs 31% for ReAct** with Qwen 2.5 72B β€” nearly double. **[The Illusion of Agentic Complexity](https://arxiv.org/abs/2606.30524)** (ICSME 2026) delivered a sobering finding: for README generation, a single-agent pipeline matched multi-agent quality while **reducing token consumption by 86% and running at 2x speed.** Multi-agent only won on structural consistency. The sobering conclusion: more agents is not uniformly better. **[The Organizational Behavior of Agentic AI](https://arxiv.org/abs/2606.30986)** (Jun 2026) was perhaps the most actionable paper. It found that human-imitation coordination forms β€” managers, committees, supervisor-worker β€” consistently underperform because they add lossy handoffs, correlated deliberation, and verification burdens. **Shared-state and adaptive forms perform better** when they make context durable, inspectable, and task-contingent. **[Multi-Head Recurrent Memory](https://arxiv.org/abs/2607.01523)** (Jul 2026) diagnosed why recurrent memory agents degrade: monolithic text-block memory causes overwrite collisions as context grows. By partitioning memory into independent heads with a select-then-update strategy β€” only one head updated per step, others shielded from overwriting β€” they improved retention from <30% to 73.96% at 896K tokens, training-free. ## Three proven alternative patterns Across the 11 papers, three patterns keep emerging: | Pattern | Key insight | When to use | |---|---|---| | **Hierarchical decomposition** | Split planning from execution; each sub-agent gets a fresh context window | Long-horizon tasks with natural subtask boundaries | | **Distributional/parallel** | Distribute input across agents, don't funnel through one | Tasks where knowledge exceeds a single context window | | **Shared-state + adaptive** | Agents share durable filesystem state, not chat messages | Tasks requiring coordination without central control | The common thread: **get data out of the context window and onto durable, inspectable storage.** Chat-based handoffs between agents are the hidden cost. A YAML file on disk that a fresh agent reads is cheaper β€” in both tokens and accuracy β€” than a 50-line natural-language summary passed through a bloated context. ## What I built: governance-worker-synthesis I replaced my orchestrator-minion system with a three-agent architecture backed by filesystem state: ``` User β†’ Governance (plans, writes task specs to .agent-state/tasks/*.yaml) β”‚ β”‚ spawns workers with: "Read task spec. Write result to file. Confirm." β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Worker 1 β”‚ β”‚ Worker 2 β”‚ β”‚ Worker 3 β”‚ ← fresh contexts β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β–Ό β–Ό β–Ό (writes to disk, returns short confirmation) .agent-state/results/*.yaml ← shared filesystem state β”‚ β–Ό Synthesis agent (fresh context, reads result files, produces final answer) β”‚ β–Ό User ← clean, focused output ``` **Governance** never does per-unit work and never synthesizes results. It only plans and delegates. Each worker brief is minimal: a task spec path and a result path. Workers write structured results to YAML files and return only a short confirmation β€” not the full result. When all workers complete, a fresh synthesis agent reads every result file and produces the final answer. The task spec schema: ```yaml # .agent-state/tasks/analyze-auth.yaml goal: audit the authentication module for security issues constraints: do not modify the database schema expected_output_path: .agent-state/results/analyze-auth.yaml context_files: [src/auth/middleware.ts, src/auth/session.ts] blocked_on: null ``` The result schema: ```yaml # .agent-state/results/analyze-auth.yaml status: ok summary: found two session fixation vulnerabilities and one missing CSRF check findings: - session tokens not rotated on login (critical) - cookie flagged httpOnly but not secure (medium) - CSRF middleware bypassed on API routes (high) files_changed: [] blockers: [] verification_gaps: - could not verify third-party OAuth callback flow end-to-end ``` The plugin I wrote (replacing the old orchestrator-minion watchdog) adds two capabilities beyond inactivity detection: 1. **Fanout completion tracking** β€” when all workers for a given fanout are done, the plugin nudges governance to spawn synthesis. 2. **Path-aware recovery** β€” when a worker stalls, the recovery prompt includes the task spec path and expected result path, so governance can inspect the state on disk before deciding what to do. ```js // Recovery prompt now includes file paths "A watched worker may be inactive. Task spec: .agent-state/tasks/analyze-auth.yaml Expected result: .agent-state/results/analyze-auth.yaml Inspect the child session, then decide: wait, re-brief, or report a blocker." ``` ## What changed The before-and-after on a typical multi-file refactor (analyze auth, fix session handling, audit deps): | | Before (orchestrator-minion) | After (governance-worker-synthesis) | |---|---|---| | Context at peak | ~85K tokens (3 minion results accumulated) | ~12K tokens (3 short confirmations) | | Handoff format | Natural language summaries | Structured YAML on disk | | Synthesis context | Same bloated orchestrator window | Fresh, clean window | | Stale worker recovery | Blind "inspect the session" | Path-aware with task spec + result file | | Orchestrator drift | Frequent β€” "just this one fix" | Impossible β€” governance can't execute | The synthesis agent is the sleeper hit. Giving it a fresh context window dedicated solely to reading result files and producing output means it never sees the governance planning phase, the worker failures, or the recovery nudges. It sees only the finished work, and its output quality reflects that. ## What still needs work **Elastic context (ACE).** The [ACE paper](https://arxiv.org/abs/2606.31564) describes a pluggable module that maintains lossless message history with adaptive compression β€” raw for recent steps, compressed abstracts for older ones, dropped for irrelevant ones. I haven't implemented this yet. For long sessions where governance itself accumulates context (e.g., a 20-worker fanout), this would be the next layer of defense. **Dependency-aware fanout.** Right now, fanout assumes all workers are fully independent. If task B needs task A's output, governance must serialize them manually. ReAcTree's dynamic tree construction could automate this. **Structured handoff standardization.** The YAML schemas I use are project-specific. There's room for a small, shared vocabulary β€” `status`, `findings`, `files_changed`, `blockers`, `verification_gaps` β€” that any agent in any project could use. ## Bottom line Orchestrator agents are a local maximum. They feel right because they mirror how humans manage teams. But the arXiv research is clear: shared-state coordination beats chat-based delegation, hierarchical decomposition beats monolithic context, and structured handoffs beat natural-language summaries. The governance-worker-synthesis pattern isn't the final answer, but it's a concrete step away from the dead end of orchestrator context bloat. If your agent system has an orchestrator that touches per-unit work or accumulates sub-agent results in its own window, the papers are clear: that's your bottleneck, not your model. --- *Papers referenced: [ClawArena-Team](https://arxiv.org/abs/2606.31174), [ExtAgents](https://arxiv.org/abs/2505.21471), [OrgAgent](https://arxiv.org/abs/2604.01020), [ReAcTree](https://arxiv.org/abs/2511.02424), [Illusion of Agentic Complexity](https://arxiv.org/abs/2606.30524), [Org Behavior of Agentic AI](https://arxiv.org/abs/2606.30986), [Multi-Head Recurrent Memory](https://arxiv.org/abs/2607.01523), [ACE](https://arxiv.org/abs/2606.31564), [ProSEA](https://arxiv.org/abs/2510.07423), [Safe Bilevel Delegation](https://arxiv.org/abs/2604.27358), [Curriculum Guided Massive MAS](https://arxiv.org/abs/2512.08545)* --- ### runtime-skill-evals-with-pi.md Skills are context files that change how coding agents behave. But do they actually help? We built a runtime eval harness to find out β€” and the answer is more interesting than "yes" or "no." The repo is at [github.com/mihaiserban/skills](https://github.com/mihaiserban/skills) β€” 29 agent skills for engineering workflows, design translation, git operations, and research. Each skill is a `SKILL.md` file with frontmatter and instructions that an agent loads on-demand. --- ## The Problem Static review tells you a skill is well-written. It doesn't tell you whether the skill changes outcomes. A skill that says "always reproduce the bug before fixing it" is good advice. But if the model already does that by default, the skill adds tokens without changing behavior. You need a runtime test: run the same task with and without the skill, grade the outputs, and measure the delta. This is what [Anthropic's skill-creator](https://github.com/anthropics/skills/tree/main/skills/skill-creator) does β€” spawn subagents with and without the skill, compare results. We wanted the same thing, but using the [pi coding agent](https://pi.dev) as our harness. --- ## The Harness The eval pipeline has three stages, all scriptable and CI-friendly: ```text eval-runner.py β†’ runs each eval with and without the skill (parallel) eval-grade.py β†’ grades outputs against assertions (batch LLM) eval-aggregate.py β†’ produces benchmark.json + HTML review ``` The runner uses pi with full skill isolation: ```bash # Without skill: zero skills loaded, no contamination pi --no-skills --no-extensions -e ~/.pi/agent/extensions/gateway \ --no-context-files --no-session \ --model gateway/planner -p "eval prompt" # With skill: only the tested skill, nothing else pi --no-skills --no-extensions -e ~/.pi/agent/extensions/gateway \ --no-context-files --no-session \ --skill /path/to/SKILL.md \ --model gateway/planner -p "eval prompt" ``` The `--no-skills` flag prevents all skill discovery. Global skill directories (`~/.agents/skills/`, `~/.pi/agent/skills/`) are physically moved during eval runs to guarantee the baseline can't cheat. The `--skill ` flag loads only the tested skill β€” additive even with `--no-skills`. For skills with dependencies (like the design orchestrator that routes to picker β†’ apply β†’ audit), the evals.json declares `skill_deps` and the runner passes multiple `--skill` flags. --- ## The Evals 25 skills, 2 evals each, 100 total runs across 8 parallel workers. Each eval has 3-7 assertions that check specific, verifiable outcomes: ```json { "skill_name": "kill-dead-code", "evals": [ { "id": 1, "name": "remove-unused-function", "prompt": "Clean up this module. I think some functions are never called...", "assertions": [ {"id": "identifies-dead", "text": "Identifies all four dead functions", "type": "quality"}, {"id": "keeps-live", "text": "Keeps the two used exports", "type": "quality"}, {"id": "warns-exports", "text": "Warns unused exports might be public API", "type": "behavior"} ] } ] } ``` Grading uses a batch LLM approach: all assertions for one eval variant go to a single `gateway/coder` call. The grader receives the model output in XML tags (to avoid code-fence collision bugs) and returns numbered PASS/FAIL verdicts. --- ## The Numbers 100 runs, 97 completed, 3 timed out (all with_skill β€” complex code generation). Here's the full benchmark: | Skill | With Skill | Without Skill | Delta | |-------|-----------|---------------|-------| | governance-fanout | 89% | 11% | **+78%** | | show-first | 85% | 15% | **+69%** | | design-md-style-audit | 67% | 0% | **+67%** | | pr-from-diff | 90% | 40% | **+50%** | | design (orchestrator) | 89% | 44% | **+44%** | | blog-post | 100% | 60% | **+40%** | | context-budget | 88% | 50% | **+38%** | | systematic-debugging | 83% | 50% | **+33%** | | revert-surgical | 100% | 78% | **+22%** | | changelog-from-diff | 100% | 80% | **+20%** | | input-validation | 100% | 80% | **+20%** | | design-md-style-apply | 83% | 67% | **+17%** | | design-taste-distiller | 50% | 33% | **+17%** | | research | 58% | 42% | **+17%** | | kill-dead-code | 71% | 57% | **+14%** | | decision-record | 100% | 89% | **+11%** | | adversarial-verify | 100% | 90% | **+10%** | | sql-review | 70% | 60% | **+10%** | | secret-scan | 36% | 27% | **+9%** | | clean-commits | 100% | 91% | **+9%** | | design-md-style-picker | 100% | 100% | 0% | | bisect-regression | 100% | 100% | 0% | | contract-test | 90% | 90% | 0% | | rebase-safely | 80% | 80% | 0% | | domain-modeling | 11% | 78% | **-67%** | **20 of 25 skills show positive delta.** 4 show no measurable difference. 1 shows a negative delta. --- ## What the Deltas Tell You **Strong positive (+40% to +78%)** β€” these skills teach structured workflows the model can't improvise. `governance-fanout` (+78%) teaches a plan β†’ delegate β†’ synthesize pattern. `show-first` (+69%) enforces wireframe-before-code. `design-md-style-audit` (+67%) provides an audit rubric the model doesn't invent on its own. **Moderate positive (+9% to +20%)** β€” the skill adds a behavior the model sometimes does but not reliably. `revert-surgical` ensures `git bisect reset` is mentioned. `changelog-from-diff` enforces a specific output format. **Zero delta** β€” the model already handles the task well, or the assertions don't capture what the skill changes. `contract-test` scores 90%/90% β€” the model writes good contract tests by default. `rebase-safely` scores 80%/80% β€” git rebase is well-known territory. **Negative delta (-67%)** β€” `domain-modeling` makes the model more cautious: it refuses to act without full domain context, which is the skill's intent but doesn't score well on synthetic prompts that provide the context upfront. The skill is correct; the eval is wrong. --- ## Running the Benchmark ```bash # Full pipeline: run β†’ grade β†’ aggregate, all skills, 8 parallel workers bash scripts/start-evals.sh --all --parallel 8 # Or step by step python3 scripts/eval-runner.py --all --parallel 8 python3 scripts/eval-grade.py --all --parallel 8 --model gateway/coder python3 scripts/eval-aggregate.py --all --output html ``` Each skill gets `eval-results/iteration-1/` with `benchmark.json`, `benchmark.md`, and a `review.html` showing per-eval outputs and assertion pass/fail. The runner handles skill dependencies β€” skills that reference other skills (like the design orchestrator routing to picker β†’ apply β†’ audit) declare `skill_deps` in their `evals.json`, and the runner loads them via multiple `--skill` flags. --- ## Bottom line Agent skills are software. They need tests. A 100-run benchmark takes ~15 minutes with 8 parallel workers and tells you exactly which skills earn their context budget and which are dead weight. The full skill pack β€” 29 skills, eval harness, and all evals β€” is at [github.com/mihaiserban/skills](https://github.com/mihaiserban/skills). Clone it, run `bash scripts/start-evals.sh --all`, and see what your skills actually do. --- ### independent-contractor.md Specializing in development and consulting for modern web and cloud-based applications, including: - ReactJS front-end development - AWS-based architecture and solutions - Node.js back-end services - Microservices architecture - Mobile application development for iOS Experienced in designing, building, and maintaining scalable applications across full-stack and cloud environments. --- ### ios-developer-skobbler-aquired-by-telenav-inc.md At skobbler, I was part of a large team of iOS and C++ developers building location-based applications powered by OpenStreetMap. Some of the products I contributed to included GPS Navigation, ForeverMap, GeoBrain, and Blitzer.de. __Contributions__ - Contributed to the development and continuous improvement of mobile navigation applications used by millions of users. - Designed and implemented reusable components to support scalability and maintainability across projects. - Performed code reviews and helped maintain high engineering and code quality standards within the team. - Collaborated closely with the QA department to identify, track, and resolve software defects. - Participated in Scrum ceremonies and agile planning sessions as part of an iterative development process. --- ### mentorship-program-itbrainiacs-apex-edu-telenav-collaboration.md __ITBrainiacs__ is a program developed by __Apex-Edu__ in collaboration with __Telenav__ , designed to identify talented students and help them reach their full potential in software engineering. The program pairs selected high-potential students with experienced software engineers in a structured 6-month one-on-one mentorship initiative. __Role & Contribution__ - Mentored students in software development fundamentals and best practices - Provided guidance on problem-solving, coding standards, and software engineering principles - Supported mentees through hands-on learning, feedback sessions, and continuous technical coaching - Helped bridge academic knowledge with real-world software development practices --- ### paternity-leavecareer-break.md Career break to focus on family. β€œYou have little kids for four years and if you miss it. It's done. That's it.” - Jordan Peterson --- ### quality-assurance-engineer-skobbler-acquired-by-telenav-inc.md __QA / Mobile Testing Responsibilities__ - Performed testing across multiple handheld devices and platforms, including Android, iPhone, BlackBerry, and Nokia devices. - Automated manual test cases for mobile platforms, primarily Android and iOS. - Designed, created, and executed test cases for mobile applications and device-specific functionality. - Built testing environments to simulate real-world usage scenarios and operating conditions. - Collected diagnostic information and extracted relevant logs to investigate and troubleshoot issues identified during testing. - Reported and tracked defects using Jira. - Maintained and updated test cases based on evolving project requirements and test plans. - Worked independently with minimal supervision while managing testing responsibilities and delivery timelines. - Provided release assessments and quality sign-offs to support production deployments. --- ### senior-ios-developer-3pillar-global.md While at 3Pillar Global I was part of a cross site team (Cluj, Timisoara and US) working on [__Geico__](https://www.geico.com "Geico")'s insurance mobile apps. This was a complex and high-responsibility project involving sensitive data such as payments and personal user information, requiring strong focus on security, reliability, and compliance. I collaborated closely with distributed teams across multiple time zones to design, develop, and deliver features while maintaining consistent quality across releases. --- ### senior-ios-developer-neosteq.md I was part of a smaller iOS engineering team working on a companion application for portable navigation devices (PNDs). Focused on real-time device communication, mapping features, and performance-critical mobile rendering. __Contributions__ - Implemented communication between PND and iOS devices using Google Protocol Buffers for efficient data serialization. - Developed Bluetooth-based communication channels between devices. - Implemented and maintained In-App Purchase functionality. - Integrated multiple web services into the mobile application. - Implemented weather alert overlays on maps using OpenGL rendering. --- ### senior-software-engineer-contractor-flutter-entertainment.md I joined Flutter Entertainment as a Contractor Senior Software Engineer, primarily working on iOS and frontend development. __Contributions:__ - Assisted the iOS team in migrating parts of existing web-based iOS applications into native iOS code, including feature implementation and code reviews for other contracting teams. - Migrated a large in-house frontend platform from AngularJS (Angular 1) to a modern ReactJS stack using Redux and React Hooks. - Rebuilt the Help & Support static website and improved the static site generation pipeline for Betfair Support. - Migrated Cash Card & Cash Card Plus frontend functionality to a new ReactJS-based frontend architecture. - Added new features to existing Vue.js and AngularJS applications, including authentication and role-based access control using Microsoft Authentication Library (MSAL). --- ### senior-software-engineer-flutter-entertainment.md __Contributions:__ - Led the complete rewrite of the in-house LivePerson JavaScript messenger library, improving architecture, maintainability, debugging capabilities, integration flexibility, and automated test coverage; successfully migrated the new implementation (v2) into production. - Implemented the Help Knowledge Service, a semantic search platform built with Python and sentence transformers, including a two-step retrieval pipeline for intelligent article discovery and ranking, as well as ingestion pipelines that process Help & Support content and store vector embeddings in a vector database for semantic retrieval and contextual search capabilities. - Contributed to the migration from on-premise infrastructure to AWS cloud environments using AWS CDK and Kubernetes (K8s). --- ### senior-software-engineer-telenav-inc.md During my time at Telenav, I worked primarily as an iOS developer on the Scout navigation client, with a strong focus on Objective-C development. I led a team of five developers, providing technical leadership and mentorship, maintaining coding standards through code reviews and documentation, and participating in technical interviews and hiring processes. My responsibilities included designing and implementing new features, improving application stability and performance, and collaborating closely with cross-functional teams to deliver navigation and location-based functionality. From mid-2016, I transitioned into a new team focused on developing a cross-platform desktop application using Qt, C++, QML, and JavaScript. --- ### blitzer_de.md ## Context While working at skobbler I had the opportunity to contribute to the Blitzer.de, the #1 speed cam application on the German App Store. It uses skobbler's map technology stack alongside Blizer.de up to date speedcam database to inform the users of upcoming speedcams. ## Responsibilities - iOS development as part of a team of 3 iOS developers - synchronize data from Blitzer.de database - C++ development, mainly integrating skobbler's C++ rendering engine for map display, and contributing to adapt it for our use case. There was no map SDK for plug and play integration. --- ### callsign.md ## Context Callsign has built a secure mobile multi-factor authentication and authorisation engine, through the introduction of patented machine-learning biometric, behavioural, geo-location and identity analysis, combined with traditional methods. ## Responsibilities - participate in Scrum meetings, and provide clear reports of the work progress - lead the development efforts on creating the interface for their Ruleset Manager using React and D3.js. The final product can be seen in the showcase video. - assist the team with code reviews --- ### creative-navy.md ## Context Creative Navy is a London based design agency which focuses on user research, user experience design and UI design. Creative Navy ranked Top-5-global UX agencies from 2017 to 2019. ## Responsibilities Create a new website from scratch following their designs. Google audit score in the 90th percentile. --- ### elho.md ## Context iPad app created for the Elho sales team to showcase all of Elho's products. Elho is a fast growing family company, specialized in plastic plant pots. ## Responsibilities - iOS development --- ### forevermap.md ## Context ForeverMap by Skobbler GmbH is an offline navigation application available for iOS (iPhone/iPod Touch/iPad). ForeverMap provides address/POI search, route calculation and Wikipedia information. OpenStreetMap Maps and POI data is the main source of information of this app. ## Responsibilities - iOS development as part of a larger team - code reviews, follow best practices - participate in daily Scrum meetings - provide mentorship to fellow collegues --- ### geobrain.md ## Context GeoBrain is a interactive geo quiz game available on the iPhone and iPad. I was the only iOS developer working on the project. Project included working with SQLite databases, networking, implementing game logic according to specified workflows, mixing OpenGL with UIKit elements. ## Responsibilities - iOS development - participate in daily Scrum meetings --- ### impetus.md ## Context Impetus is a platform for reward based advertising and the cryptocurrency to run it. Meet Impetus One and the Nudge token. It’s where brands meet customers who actually want them in their lives. Advertisers come up with and pay for brand missions consumers complete. The fee is split between the customer, and the ad publisher where the customer joined the mission, with the smallest share for the platform. Blockchain brings in low transaction fees, high velocity and geographically unbound means of currency distribution. ## Responsibilities - ReactJS development --- ### next_nomads.md ## Context NextNomads is designed to be a network of coworking spaces around the world, where freelancers, entrepreneurs and urban nomads can work whenever needed. The app lets users access the list of spaces via a map and shows the nearest offices and their amenities. It also gives access inside the offices, letting the users in via account token/wireless unlocking of the gates/doors. ## Responsibilities - iOS development - testing - provide clear progress reports to the client --- ### scout_global.md ## Context Scout is a map and navigation app for iOS devices (iPhone and iPad with cellular support) built on top of OpenStreetMap. Features - online and offline map - shows traffic on the map with data by Inrix - shows speed cameras that were reported on the german platform blitzer.de ## Responsibilities - lead a team of 5 iOS developers - iOS development - mentorship - code reviews, follow best practices - participate in daily Scrum meetings - maintain clear communication with the C++ team, QA Team and Project Management --- ### we_style.md ## Context WeStyle is a social media application which offers instant style feedback plus discovery options that shift style uncertainty into total outfit confidence. It alows the user to share his fashion style with a like minded community, and receive feedback. ## Responsibilities - iOS development - project managment ---