<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Vassilis Poursalidis - TalentLMS Tech Blog</title><link>https://blog.talentlms.io/authors/vpoursalidis/</link><description>Posts by Vassilis Poursalidis - Insights and stories from the TalentLMS engineering team. Technical deep-dives, team culture, and lessons learned building learning management software.</description><language>en-US</language><managingEditor>noreply@talentlms.com (TalentLMS Engineering Team)</managingEditor><webMaster>noreply@talentlms.com (TalentLMS Engineering Team)</webMaster><lastBuildDate>Mon, 07 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.talentlms.io/authors/vpoursalidis/feed.xml" rel="self" type="application/rss+xml"/><image><url>https://blog.talentlms.io/images/logo/logo.svg</url><title>TalentLMS Tech Blog</title><link>https://blog.talentlms.io/</link></image><item><title>Why We Built Flipster</title><link>https://blog.talentlms.io/posts/flipster-library/</link><pubDate>Mon, 07 Sep 2026 00:00:00 +0000</pubDate><dc:creator>Vassilis Poursalidis</dc:creator><guid>https://blog.talentlms.io/posts/flipster-library/</guid><description>Flipster is our open source library for using OpenFeature safely in production. It came out of adopting trunk-based
development, where feature flags stop being a nice to have and become the thing that lets a deploy and a release be
two separate decisions.
It wraps the OpenFeature SDK behind an interface you can inject and test like any other dependency, and puts a circuit
breaker in front of the provider. Defaults move out of the call sites and get declared once, in one place, so a
degraded flag provider never becomes a production incident.</description><enclosure url="https://blog.talentlms.io/images/posts/flipster-library.png" type="image/png"/><media:content url="https://blog.talentlms.io/images/posts/flipster-library.png" medium="image"><media:title type="plain">Abstract painted composition of several diverging brushstrokes merging into a single thick trunk line on a warm background</media:title><media:description type="plain">Abstract painted composition of several diverging brushstrokes merging into a single thick trunk line on a warm background</media:description></media:content><content:encoded><![CDATA[<img src="https://blog.talentlms.io/images/posts/flipster-library.png" alt="Abstract painted composition of several diverging brushstrokes merging into a single thick trunk line on a warm background" style="max-width: 100%; height: auto; margin-bottom: 1.5em;" /><p style="margin-bottom: 1.5em; padding: 1em; background-color: #f5f5f5; border-left: 4px solid #0066cc;"><strong>Vassilis Poursalidis</strong>, TalentLMS Engineering Director<br/>Vassilis has nearly 20 years of experience working in diverse projects in the technology industry, with a strong record of leadership and technical expertise.

He is currently an Engineering Director …</p><p>Feature flags sound simple: check a boolean, branch on it. But the moment you adopt them at any real scale, your
production environment depends on an external service for something that sits on nearly every request path. The
API is trivial, <code>isEnabled('some-flag')</code>, but the operational reality behind that single call is not. The provider is a
network dependency. It can be slow. It can be unreachable. It can go down entirely. And when it does, your feature
flag check, a piece of infrastructure meant to make your releases <em>safer</em>, can become the thing that takes the entire
service down.</p>
<p>This effort started with a decision that had nothing to do with flags: <em>we are moving to trunk-based development</em>. Merging to
<code>main</code> continuously, without long-lived feature branches, only works if you have a way to ship unfinished or risky code
<strong>dark</strong>; and be able turn it on, separately from the deployment. That&rsquo;s what feature flags are for, but &ldquo;we need feature flags&rdquo;
doesn&rsquo;t tell you which flag system to build on, and we didn&rsquo;t want to build one ourselves or lock ourselves into a
single vendor&rsquo;s SDK. Both of those turn out to hinge on who owns a flag, which is a section of its own below. (That
move to trunk-based development is its own story, and one we&rsquo;ll get into in a follow-up post.)</p>
<p>That&rsquo;s what led us to <a href="https://openfeature.dev" target="_blank" rel="noopener noreferrer">OpenFeature</a>
: a vendor neutral, open standard for flag evaluation,
backed by the CNCF rather than any one provider. It solves the part we didn&rsquo;t want to solve ourselves: a common interface
regardless of which flag backend sits behind it. But adopting the standard didn&rsquo;t answer the questions that came right
after: do we inject it instead of reaching for a global? What happens to our checkout flow if the flag provider
is slow? And, most importantly, how do we test the failure case?</p>
<p>Flipster is our answer to those three questions, written at the start of
adopting OpenFeature and trunk-based development together, not after years of running either.</p>
<p>There&rsquo;s a second thing worth naming: Flipster is a product of <a href="/posts/spec-driven-development/" target="_blank" rel="noopener noreferrer">Spec-Driven Development</a>
,
a practice we have been running internally for over a year now. This wasn&rsquo;t us trying it out for the first time; it was
the discipline showing up in a public artifact. We wrote the specification (the behavior table shown below, the guarantees around defaults, the failure
semantics) before implementation, and let the spec drive what got built. The <code>.aidlc</code> directory in the repo is a
visible trace of that process. It&rsquo;s part of why the README reads the way it does: the behavior was decided on paper
first, which made it possible to be precise about things like &ldquo;an evaluation never throws because a provider is unwell&rdquo;
instead of discovering that guarantee informally over time.</p>
<h2 id="two-owners-one-switch">Two owners, one switch</h2>
<p>A feature flag has two lifecycles: Ownership of the flag and ownership of its exposure. Each of them belong to different people.</p>
<p><strong>Engineering owns the flag.</strong> We introduce it when the work starts; we are responsible for the code on both sides of
it, and more importantly we are responsible for removing it. A flag that outlives its purpose is a permanent branch in
the code: a small compounding tax on every change that touches it.</p>
<p><strong>Product owns the exposure.</strong> When the feature gets activated, for which accounts, in what order, whether it widens or gets
pulled back. None of those are engineering calls, and none of them should consume engineering capacity. We used to be
involved because the deployment <em>was</em> controlling the flags and the deployment was ours.</p>
<p>Separating those two concerns is what removes this unnecessary link. Engineering decides whether the switch exists and
when it stops existing. Product decides in which state it is and for whom. Neither has to negotiate with the other&rsquo;s
calendar to do its own job.</p>
<p>Both owners have to be in the room for the build-versus-buy question. A flag system that satisfies only engineering is
a weekend&rsquo;s work and useless the first time product wants to widen a rollout without filing a ticket. One that
satisfies only product gets picked on the demo, and nobody asks whether it speaks OpenFeature until its SDK is already
at every call site. Both halves have to be answered by the same decision, which is most of why we neither wrote our own
nor shopped on features alone.</p>
<p>The same pair is the reason the switch has to survive a change of provider. Whatever backend we run today, those two
lifecycles outlive it: engineering will still introduce and remove flags, product will still decide exposure and
timing. Neither of those should have to be renegotiated because we moved to a different vendor. That is the practical
argument for a neutral API rather than a vendor&rsquo;s SDK; a provider swap stays a swap, instead of turning into a
conversation about who owns what.</p>
<h2 id="three-problems-one-library">Three problems, one library</h2>
<p>Once flags are owned this way, the surrounding tooling has to earn that trust.</p>
<h3 id="problem-one-the-sdk-wants-to-be-a-singleton">Problem one: the SDK wants to be a singleton</h3>
<p>OpenFeature&rsquo;s idiomatic entry point is a process global. That&rsquo;s a reasonable default for the SDK itself, but it&rsquo;s an
awkward fit for any codebase that takes dependency injection seriously. Global state resists testing, resists swapping
implementations per environment, and quietly couples your domain code to a specific initialization order.</p>
<p>Flipster&rsquo;s fix here is almost boring: it wraps the SDK behind a narrow interface you inject like anything else. Domain
code depends on a <code>FlagEvaluator</code>, not on a global. That&rsquo;s it. It&rsquo;s not a clever abstraction, it&rsquo;s the absence of one.</p>
<h3 id="problem-two-a-flag-check-is-a-network-call-on-your-hot-path">Problem two: a flag check is a network call on your hot path</h3>
<p>This is the one that actually hurts in production. A flag provider is, almost always, a service you talk to over the
network. Every <code>isEnabled()</code> call is a request that can be slow or can fail, and if that call sits in a hot path (say,
checkout, or auth), a degraded flag provider can degrade <em>everything downstream of it</em>. Even though the flag itself was
never the point of the request.</p>
<p>The standard answer to &ldquo;a dependency might be slow or down&rdquo; is a circuit breaker, and that&rsquo;s what Flipster puts in
front of the provider. Flipster doesn&rsquo;t implement its own breaker logic, it defines a small port and ships an adapter
over the existing <a href="https://github.com/ackintosh/ganesha" target="_blank" rel="noopener noreferrer">Ganesha</a>
 library, so you get a proven implementation rather
than a bespoke one.</p>
<p>But the breaker alone isn&rsquo;t the interesting part. The interesting part is what happens <em>before</em> the breaker even trips,
because a single failed request and a fully open circuit are different situations, and conflating them is where a lot
of flag handling code goes wrong.</p>
<p>In fact, they need different responses. One failed evaluation should be absorbed on the spot: you serve something
sensible and carry on, because a provider that hiccups once is still a provider. An open circuit is a different claim
entirely, that this provider is known to be unwell, and the right response is to stop calling it at all. Flipster does
both and in that order. The declared default answers each individual failure the moment it happens, and the breaker,
once tripped, stops the wasted network attempts. Watch a real provider get killed underneath it and you see the
defaults start appearing several requests before the circuit actually opens.</p>
<h3 id="problem-three-defaults-that-disagree-and-failure-paths-nobody-can-test">Problem three: defaults that disagree, and failure paths nobody can test</h3>
<p>Here&rsquo;s the pattern we wanted to avoid before it ever had the chance to happen: a feature flag call with a fallback
value passed at the call site (<code>getBooleanValue('new-checkout', false)</code>), repeated at a few more call sites, each with
its own guess at the right default. A few months later, nobody remembers which fallback is the &lsquo;official&rsquo; one, and nobody
has a reliable way to force the provider into a degraded state to check what actually happens. We hadn&rsquo;t lived through
that yet, we just didn&rsquo;t want to build it that way in the first place.</p>
<p>Flipster addresses this with a single idea: <strong>declare defaults once, separately from call sites.</strong> <code>withDefaults()</code> is
the one place in your codebase a flag&rsquo;s fallback value lives. Call sites don&rsquo;t carry a default; they can&rsquo;t disagree
with each other, because there&rsquo;s nothing to disagree about.</p>
<p>That makes <code>withDefaults()</code> more than a fallback map. It is the list of flags your code is allowed to ask about at all:
a flag missing from it throws even when the provider is perfectly healthy and would have answered. Adopting Flipster
means accepting that every flag gets declared, not just the ones you expect to fail.</p>
<p>This produces a small but deliberate behavior table:</p>
<table>
  <thead>
      <tr>
          <th>Situation</th>
          <th>Value served</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Provider answers</td>
          <td>the live value, untouched</td>
      </tr>
      <tr>
          <td>Provider errors, flag declared</td>
          <td>the declared default</td>
      </tr>
      <tr>
          <td>Circuit open</td>
          <td>the declared default (provider not even called)</td>
      </tr>
      <tr>
          <td>Flag not declared</td>
          <td>throws: never silently <code>false</code></td>
      </tr>
  </tbody>
</table>
<p>The middle two rows are the distinction from the previous section, written down. An error while the circuit is still
closed costs a full round trip and then falls back; an open circuit skips the call altogether. Same value served,
different amount of your request budget spent getting there.</p>
<p>That last row is the one people notice first, and argue about. Flipster intentionally does <em>not</em> let an undeclared flag
silently evaluate to <code>false</code>. A typo&rsquo;d flag key that quietly reads as &ldquo;feature disabled&rdquo; is indistinguishable from a
feature someone deliberately turned off, and nobody investigates a flag that looks correctly configured. So it throws,
with a message that suggests the key you probably meant. Declarations are also validated when the stack is built, not
at first use, so a malformed defaults map fails during deployment instead of surprising someone during an incident.</p>
<p>And because failure behavior is now fully deterministic (a declared default, injected doubles, a controllable breaker),
the failure paths become ordinarily testable, instead of the kind of thing you only find out about in production. That
determinism is easier to see than to describe. The diagram below traces a single <code>isEnabled()</code> call through both checks:
whether the flag is declared, and whether the circuit is open; and shows exactly which of the four rows in the table
above it lands on.</p>
<div class="mermaid">
  
---
title: Diagram - How a flag check decides what to return
---
%%{init: {'look': 'handDrawn'}}%%
flowchart TD
A["Domain code calls flags->isEnabled('flag-key')"] --> B{Flag declared<br/>in withDefaults?}
B -- No --> C["Throw<br/>(suggests likely key)"]
B -- Yes --> D{Circuit breaker<br/>open?}

    D -- "Yes (provider known unwell)" --> E["Return declared default<br/>— provider not even called —"]

    D -- No --> F["Call provider over network"]
    F --> G{Provider<br/>responds?}

    G -- Success --> H["Return live value"]

    G -- "Error / timeout" --> I["Return declared default<br/>(full round trip spent)"]
    I --> J{Failure threshold<br/>reached?}
    J -- Yes --> K["Breaker trips open"]
    J -- No --> L["Breaker stays closed"]

    style C fill:#f8d7da,stroke:#c0392b
    style E fill:#fff3cd,stroke:#b7891a
    style I fill:#fff3cd,stroke:#b7891a
    style H fill:#d4edda,stroke:#2e7d32
    style K fill:#f8d7da,stroke:#c0392b

</div>

<p>That determinism only helps if you can actually exercise it in a test, so Flipster ships its own test doubles in the
runtime package, no extra dependency to pull in. An <code>InMemoryProvider</code> stands in for a real backend, a
<code>RecordingProvider</code> wraps any provider and tracks how many times it was actually called, a <code>ThrowingProvider</code> simulates
a hard failure, and a <code>ControllableCircuitBreaker</code> lets a test pin the circuit open directly instead of trying to
manufacture enough failing requests to trip a real threshold. That last one matters more than it sounds: without it,
&ldquo;test what happens when the circuit is open&rdquo; means fighting your breaker&rsquo;s timing and thresholds just to get into the
state you actually wanted to assert on.</p>
<p>Put together, all three fixes are one builder call at the edge of the application and an injected dependency
everywhere else:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">use</span> <span style="color:#a6e22e">Epignosis\Flipster\Flipster</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$flags <span style="color:#f92672">=</span> <span style="color:#a6e22e">Flipster</span><span style="color:#f92672">::</span><span style="color:#a6e22e">for</span>($provider)          <span style="color:#75715e">// any OpenFeature provider
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withDefaults</span>([
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;new-checkout&#39;</span>     <span style="color:#f92672">=&gt;</span> <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;checkout-variant&#39;</span> <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">&#39;control&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;rate-limit-rpm&#39;</span>   <span style="color:#f92672">=&gt;</span> <span style="color:#ae81ff">100</span>,
</span></span><span style="display:flex;"><span>    ])
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">withBreaker</span>($breaker, <span style="color:#e6db74">&#39;flags&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">evaluator</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Domain code depends on FlagEvaluator, and on nothing else.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">if</span> ($flags<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">isEnabled</span>(<span style="color:#e6db74">&#39;new-checkout&#39;</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>Note what the call site does not contain: a default value, a <code>try</code>/<code>catch</code>, or any hint that a network is involved.</p>
<h2 id="what-we-deliberately-left-out">What we deliberately left out</h2>
<p>Flipster does not cache. That was a conscious decision, not an oversight: the provider you choose already owns the flag
data&rsquo;s lifecycle and its invalidation signals, and a cache bolted on from the outside can only guess at correctness
with a TTL. Caching is a job for the layer that actually knows when the data changed.</p>
<p>We also didn&rsquo;t build another flag evaluation API. Flipster composes with OpenFeature; it doesn&rsquo;t compete with it. If
you want per-call defaults instead of the declared-once model, the underlying OpenFeature client is still right there.</p>
<h2 id="the-shape-of-the-fix">The shape of the fix</h2>
<p>None of these three problems are novel, and none of the fixes are exotic: DI over globals, a circuit breaker over a
flaky dependency, single sourced defaults over scattered ones. That&rsquo;s sort of the point. This is early. Flipster is
new, born directly out of adopting trunk-based development and OpenFeature together, and built the way we build
everything now, spec first. We&rsquo;d rather get this resilience layer right once, in the open, before we&rsquo;ve written &ldquo;make
the flag provider safe to depend on&rdquo; glue code in five different places and had to reconcile it later.</p>
<p><strong>The quick facts:</strong></p>
<ul>
<li>344 tests, 654 assertions, 99+% coverage: the failure paths are covered, not just the happy one</li>
<li>MIT licensed, and public because none of the problems it solves are specific to us</li>
<li>Batteries included: a containerized toolchain where Docker is the only prerequisite, CI across PHP 8.1 through 8.5,
runnable examples, and documentation that covers the traps rather than just the API</li>
</ul>
<p>Flipster is on GitHub at <a href="https://github.com/epignosis/flipster" target="_blank" rel="noopener noreferrer">epignosis/flipster</a>
, MIT licensed, and installable
via Composer:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>composer require epignosis/flipster
</span></span></code></pre></div><p>If you&rsquo;re already on OpenFeature and just want the resilience layer without adopting anything else, <code>docs/migrating.md</code>
in the repo walks through adopting it incrementally in a codebase that&rsquo;s already using the SDK directly.</p>
]]></content:encoded></item><item><title>Redesigning our Hiring Process to Scale Team Ownership</title><link>https://blog.talentlms.io/posts/redesigned-hiring/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0000</pubDate><dc:creator>Vassilis Poursalidis</dc:creator><guid>https://blog.talentlms.io/posts/redesigned-hiring/</guid><description>Our hiring process was not broken in an obvious way. It produced reasonable outcomes and we had learned to live with
its inefficiencies. But over time the cost of running it became impossible to justify. I could have looked the other
way. But I have always believed in leaving the battlefield better than I found it. This is the story of how we
identified its shortcomings, redesigned the process around our actual constraints, and handed ownership of it to
the entire team.</description><enclosure url="https://blog.talentlms.io/images/posts/redesigned-hiring.png" type="image/png"/><media:content url="https://blog.talentlms.io/images/posts/redesigned-hiring.png" medium="image"><media:title type="plain">Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background</media:title><media:description type="plain">Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background</media:description></media:content><content:encoded><![CDATA[<img src="https://blog.talentlms.io/images/posts/redesigned-hiring.png" alt="Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background" style="max-width: 100%; height: auto; margin-bottom: 1.5em;" /><p style="margin-bottom: 1.5em; padding: 1em; background-color: #f5f5f5; border-left: 4px solid #0066cc;"><strong>Vassilis Poursalidis</strong>, TalentLMS Engineering Director<br/>Vassilis has nearly 20 years of experience working in diverse projects in the technology industry, with a strong record of leadership and technical expertise.

He is currently an Engineering Director …</p><p>Engineering organizations usually notice scaling problems in obvious places first: infrastructure, delivery,
communication, coordination. Hiring processes rarely make that list. At least not initially.</p>
<p>For a long time, our hiring process seemed to work reasonably well. Candidates received a take-home assignment,
submitted their solution, and we reviewed it internally before moving to follow-up discussions. It was a familiar
setup, widely used across the industry, and it produced decent hiring outcomes: hires we felt good about, at a pace
that felt manageable.</p>
<p>But over time, we realized we had been too focused on running the process instead of stopping to question it. The
problem was the operational cost and fragility of the process itself; the reviewer fatigue, the inconsistency that
crept in when people were stretched thin, and the brittleness of a process that depended on too few hands.</p>
<p>What started as a discussion about replacing take-home assignments with live coding eventually became something much
bigger:</p>
<p><strong>How do you redesign a process so ownership can scale beyond a small number of people?</strong></p>
<p>This article is not really about live coding versus take-home assignments. It is about simplifying an important
engineering process so it becomes teachable, repeatable, scalable, and collectively owned by the team.</p>
<h2 id="what-running-the-process-taught-us">What Running the Process Taught Us</h2>
<p>We did not sit down one day and decide to redesign our hiring process. The realization came gradually, built up
through the friction of running it: patterns we kept noticing, problems we kept deferring, and questions we kept
asking ourselves after every hiring cycle. Eventually we had accumulated enough signal that the direction was clear.</p>
<h3 id="the-process-we-inherited">The Process We Inherited</h3>
<p>When new people became involved in hiring, the onboarding material was essentially the assignment PDF itself. The
mechanics were undocumented. The decision-making even more so. The real evaluation criteria lived in the heads of
the people already running the interviews, built up through shadowing discussions, reviewing past submissions, and
years of accumulated intuition.</p>
<p>That worked while only a few people were deeply involved. But it created a fragile dependency. Understanding what
trade-offs mattered, which shortcuts were acceptable, what architectural decisions were red flags, and how to
distinguish strong engineering from polished presentation - none of that was captured in any scalable way. What
started as an onboarding inconvenience eventually became a structural bottleneck.</p>
<h3 id="the-hidden-cost-of-the-process">The Hidden Cost of the Process</h3>
<p>The workflow looked simple on paper: send the assignment, wait for the submission, review the solution, schedule a
follow-up, decide. In practice it was significantly more complicated. Solutions required lengthy internal discussions.
Different reviewers focused on different aspects. Calibration took time. The process generated substantial asynchronous
overhead, and because it depended on a small number of experienced people, progress bottlenecked around availability.
We even began using AI to help review submissions, but it reduced the load without changing the underlying problem.</p>
<p>The result was a hiring cycle that could easily stretch close to a month. Internally, it consumed considerable senior
engineering bandwidth. Externally, it created a poor candidate experience.  Candidates frequently told us the
assignment was too large, and many requested extensions just to complete it. Also strong candidates rarely pause their
search while waiting for us to finish our internal discussions, and in a competitive hiring environment, long feedback
loops mean losing good people before the process is even over.</p>
<h3 id="ai-started-shifting-the-ground">AI Started Shifting the Ground</h3>
<p>Around the same time, another shift was becoming harder to ignore. AI-assisted coding changed the nature of take-home
assignments. Not because candidates were doing something wrong, and not because we discourage AI. We actively promote
its use. The problem was that the format increasingly optimized for polished output rather than observable thinking. We
found ourselves spending more time validating depth of understanding than discussing engineering decisions.</p>
<p>Some submissions looked exceptionally strong, but follow-up conversations revealed significant gaps. In one case, a
candidate declined to share their screen while walking us through their solution, making it difficult to assess how
familiar they actually were with the code they had submitted. The assignment no longer evaluated engineering capability
alone. It now required us to separately evaluate authorship confidence and depth, and the process became heavier
precisely when we needed it to become lighter.</p>
<h3 id="we-were-evaluating-the-wrong-things">We Were Evaluating the Wrong Things</h3>
<p>As we re-examined the process, another realization we had been slow to confront emerged. The assignment required
substantial boilerplate and project setup work just to bootstrap a solution. Those are genuine engineering skills. But
we had to ask ourselves: how representative was this of the work engineers actually do in our organization?</p>
<p>We are a product-oriented company. Our engineers spend most of their time evolving existing systems, reasoning about
trade-offs, debugging production issues, and iterating on long-lived codebases. They rarely bootstrap entirely new
projects from scratch. Over time we recognized that our interview process had been rewarding greenfield setup
efficiency rather than collaborative product engineering.</p>
<h3 id="the-real-bottleneck-was-ownership">The Real Bottleneck Was Ownership</h3>
<p>At this point, the conversation stopped being about interview formats. The real issue was organizational scalability.
Only a small number of people could confidently evaluate submissions. The process depended on context and reviewer
experience that were difficult to transfer. Scaling hiring meant scaling dependency on the same individuals.</p>
<p>That led us to a broader realization: complexity naturally concentrates ownership. We were not simply trying to improve
interviews. We were trying to distribute decision-making across the engineering organization. But delegation becomes
extremely difficult when a process is ambiguous, context-heavy, cognitively expensive, and dependent on intuition that
is hard to teach. We could not scale ownership of a process that only a few people knew how to execute consistently.
That became the real problem to solve.</p>
<h2 id="from-problem-to-process">From Problem to Process</h2>
<p>Once we framed the problem correctly, our goals became much clearer: reduce the hiring cycle from close to a month to
a few days or a week, reduce coordination overhead, make interviewer onboarding easier, improve evaluation consistency,
and create a process that reflected our actual engineering reality. These were not small adjustments. They pointed
toward a fundamentally different kind of process. That is where live coding entered the discussion.</p>
<p>We chose live coding not because it is universally superior or because take-home assignments are inherently flawed, but
because it aligned better with the constraints we were trying to solve. The biggest advantage was not speed. It was
shared context. Instead of reviewing a polished artifact asynchronously, interviewers and candidates collaborate in
real time. We observe problem decomposition, communication, trade-off reasoning, and adaptability. The signal is more
immediate and easier to calibrate across interviewers.</p>
<p>Switching to live coding was only part of the work. The other part was building a process rigorous enough to be
distributed. We developed a full playbook covering both technical and communication dimensions of evaluation.
Assignments come with boilerplate already in place, and we maintain a pool of exercises calibrated to each seniority
level. Interviewers work from a structured scorecard, a feedback form helps us measure how the process is performing
over time, and guidance is available for anyone running interviews for the first time. The process is aligned with HR
so handoffs are clean. What used to live in the heads of a few people is now embedded in the process itself.</p>
<p>The deeper lesson from this experience had very little to do with hiring specifically. Delegation is rarely blocked
by willingness. More often it is blocked by ambiguity. Organizations frequently try to distribute ownership without
first simplifying the underlying process, then wonder why only a small number of people can operate it effectively. A
process becomes scalable when expectations are explicit, onboarding is teachable, and cognitive overhead is reduced.
Simplification is not about removing rigor. It is about removing unnecessary operational friction.</p>
<p>The most obvious improvement was speed, but the more important improvement was organizational. Hiring is no longer the
responsibility of a small group. It is a capability owned by the team. Eight additional engineers are now actively
involved in the process, and that number continues to grow. Even the steering of the process itself has been handed to
the team. That is the real outcome we were aiming for.</p>
<h2 id="trade-offs-still-exist">Trade-offs Still Exist</h2>
<p>None of this means live coding is without limitations. Some candidates perform better in asynchronous environments,
interview stress is real, and calibration and interviewer training still matter. To ease some of that pressure, we
recently started sharing the repository a day in advance. Not the assignment itself, which remains a surprise, but
enough context for candidates to familiarize themselves with the codebase before the session begins. We also encounter
practical friction that is harder to control. Candidates occasionally ghost scheduled sessions, which introduces its
own scheduling overhead. We are still learning and adjusting as we go.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>This journey started as a discussion about replacing take-home assignments. It ended as a lesson about scalability,
delegation, and organizational design. As engineering organizations grow, the challenge is not only building better
systems. It is building processes that can be collectively owned. That often requires simplifying things that
previously depended on experience, intuition, and hidden context. Because sustainable organizations are not built
around a few people carrying critical processes indefinitely. They are built when important work becomes teachable,
repeatable, and distributed across the team.</p>
]]></content:encoded></item><item><title>Selling Tactics Through the Lens of an Engineer</title><link>https://blog.talentlms.io/posts/selling-to-engineers/</link><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><dc:creator>Vassilis Poursalidis</dc:creator><guid>https://blog.talentlms.io/posts/selling-to-engineers/</guid><description>Modern B2B sales has evolved from annoying persistence into systematic manipulation, using tactics that exploit identity, manufacture intimacy, and weaponize professional boundaries.
These approaches persist because the conversion rate justifies burning bridges with the majority of prospects.
This article dissects the playbooks from an engineer's perspective, revealing why they work, who they target, and how we can collectively demand better.</description><enclosure url="https://blog.talentlms.io/images/posts/selling-to-engineers.png" type="image/png"/><media:content url="https://blog.talentlms.io/images/posts/selling-to-engineers.png" medium="image"><media:title type="plain">Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background</media:title><media:description type="plain">Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background</media:description></media:content><content:encoded><![CDATA[<img src="https://blog.talentlms.io/images/posts/selling-to-engineers.png" alt="Abstract painted path with a developer trying to avoid the sirens of sales pitches on a warm background" style="max-width: 100%; height: auto; margin-bottom: 1.5em;" /><p style="margin-bottom: 1.5em; padding: 1em; background-color: #f5f5f5; border-left: 4px solid #0066cc;"><strong>Vassilis Poursalidis</strong>, TalentLMS Engineering Director<br/>Vassilis has nearly 20 years of experience working in diverse projects in the technology industry, with a strong record of leadership and technical expertise.

He is currently an Engineering Director …</p><p>Almost everyone has experienced it: the relentless stream of LinkedIn connection requests, the &ldquo;just following up&rdquo; emails, the messages that start with &ldquo;Hey Vassilis!&rdquo; as if you&rsquo;re old friends. Some are merely persistent. Others cross into territory that feels manipulative, even offensive.</p>
<p>For years, I sat on the receiving end of these approaches, treating them as little more than background noise. My strategy was simple: <strong>ignore and move on.</strong> I viewed these messages as a byproduct of having an online presence; annoying, but harmless. However, lately, the tactics have shifted. I began seeing patterns that moved beyond mere persistence and into calculated manipulation. <strong>The uncomfortable truth? They work.</strong> And they work often enough to justify their continued use.</p>
<p>This crossing of the threshold (from annoying, to deceptive) is what finally prompted me to speak up. It is one thing to be sold to; it is another to be engineered. Let&rsquo;s dissect the mechanics of these modern B2B playbooks, why they persist despite the friction they cause, and what this evolution reveals about the current and future state of sales.</p>
<h2 id="the-standard-playbooks">The Standard Playbooks</h2>
<h3 id="the-multi-touch-sequence">The Multi-Touch Sequence</h3>
<p>This is sales 101: contact a prospect across multiple channels over 2-3 weeks. The pattern is based on the widely-cited statistic that it takes 7-8 &ldquo;touches&rdquo; to get a response from a cold prospect. The persistence isn&rsquo;t personal, it&rsquo;s systematic.</p>
<h3 id="the-value-first-approach">The Value-First Approach</h3>
<p>Rather than leading with a pitch, they offer something first: a relevant article, an invitation to a webinar, a whitepaper, or benchmark report. By &ldquo;providing value&rdquo; upfront, they create a subtle obligation and establish themselves as a helpful resource rather than just another vendor. The ask comes later, after you&rsquo;ve presumably been warmed up by their generosity.</p>
<h3 id="the-problem-aware-template">The Problem-Aware Template</h3>
<p>These messages reference specific pain points supposedly common to software engineering departments at companies like yours:</p>
<blockquote>
<p>I noticed companies like [company name] often struggle with scaling engineering teams / managing technical debt / attracting senior talent&hellip;</p>
</blockquote>
<p>Here&rsquo;s the thing: they&rsquo;re using industry generalizations rather than researching your actual situation.</p>
<h3 id="the-social-proof-play">The Social Proof Play</h3>
<blockquote>
<p>We work with [competitor or similar product], and they&rsquo;ve seen a 40% improvement in deployment velocity.</p>
</blockquote>
<p>This simultaneously creates legitimacy (if our competitors use them, maybe we should) and Fear Of Missing Out - FOMO (are we falling behind?). Sometimes the social proof is genuine. But most of the time they&rsquo;re cherry-picked, exaggerated, or strategically vague.</p>
<h3 id="the-trigger-event-approach">The Trigger Event Approach</h3>
<p>These reps are monitoring your company for signals: funding announcements, job postings for senior engineers, product launches, conference appearances. They time their outreach for moments when you might actually need their solution. You may think this is actually one of the more legitimate approaches (at least they&rsquo;re doing their homework!), but it still means your company&rsquo;s activities and your own personal activities are being fed into sales automation systems across dozens of vendors.</p>
<h3 id="other-common-approaches">Other Common Approaches</h3>
<p>Beyond these core playbooks, you&rsquo;ll encounter several other variations:</p>
<ul>
<li><strong>The &ldquo;Breakup&rdquo; Email</strong>: After no response, they send a final message - &ldquo;I&rsquo;ll assume this isn&rsquo;t a priority and remove you from my list&rdquo; - designed to trigger guilt.</li>
<li><strong>The &ldquo;Quick Question&rdquo; Hook</strong>: Messages with sentences suggesting a brief ask when it&rsquo;s actually a full sales pitch waiting on the other side.</li>
<li><strong>The Survey/Research Request</strong>: &ldquo;We&rsquo;re conducting research on engineering leadership&rdquo;, but the research mysteriously reveals you need their offering.</li>
<li><strong>The Comparison/Audit Offer</strong>: &ldquo;Free assessment&rdquo; of your current infrastructure, that&rsquo;s really just sales qualification disguised as service.</li>
<li><strong>The Latest Hype Integration Play</strong>: &ldquo;Your customers are demanding AI features&rdquo;, the latest hype cycle where every vendor suddenly has an AI solution that will help you add intelligence to your product.</li>
</ul>
<p>And of course countless other baiting techniques are waiting for you out there.</p>
<h2 id="when-playbooks-turn-manipulative">When Playbooks Turn Manipulative</h2>
<p>The tactics above are standard, perhaps even acceptable. But there&rsquo;s a darker set of approaches that cross ethical lines.</p>
<h3 id="the-escalation-ladder-from-polite-to-aggressive">The Escalation Ladder: From Polite to Aggressive</h3>
<p>Watch as the tone shifts across this sequence:</p>
<ul>
<li><strong>Touches 1-2</strong>: Friendly, helpful tone. &ldquo;I&rsquo;d love to show you how we&rsquo;re helping companies like yours&hellip;&rdquo;</li>
<li><strong>Touches 3-4</strong>: Mild pressure. &ldquo;Just circling back on my previous message&hellip;&rdquo;</li>
<li><strong>Touches 5-6</strong>: Guilt or challenge. &ldquo;I haven&rsquo;t heard back - is this not a priority for you right now?&rdquo;</li>
<li><strong>Touches 7+</strong>: Aggressive ultimatum. &ldquo;Should I assume you&rsquo;re not the right person?&rdquo; or &ldquo;Can I close your file?&rdquo;</li>
</ul>
<p>The psychology is deliberate: create urgency or trigger a response through discomfort. Some Sales Development Reps (SDRs) are explicitly taught that a &ldquo;no&rdquo; is better than silence, because it&rsquo;s a response they can log and report to their manager.</p>
<h3 id="the-gatekeeper-bypass">The Gatekeeper Bypass</h3>
<p>You respond to a cold outreach stating that you are not the right person, or perhaps you choose not to respond at all. Instead of accepting this boundary, they counter: &ldquo;I understand - who should I speak with instead?&rdquo; or &ldquo;Would you mind introducing me to the person who handles this?&rdquo;</p>
<p>Think about the audacity here. You have clearly indicated a lack of interest or relevance, yet their response is to dismiss your boundary and <strong>deputize you into their sales force</strong>. By asking for a direct introduction or a colleague&rsquo;s contact details, they are asking you to compromise your internal network for their commercial gain.</p>
<p>In any other context, providing internal contact paths to an unverified external actor would be flagged as a security risk. In sales, it’s framed as &ldquo;helpfulness.&rdquo; You should not, under any circumstances, provide the details of your colleagues to a cold caller. If you genuinely believe an offering has merit, you may choose to discuss this internally with your company and then decide how to proceed.</p>
<h3 id="manufactured-intimacy">Manufactured Intimacy</h3>
<p>Messages that open with &ldquo;Hey [First Name]!&rdquo; and casual language as if you&rsquo;re already acquainted. Or worse: &ldquo;We&rsquo;re delighted to have met you at [conference name]!&rdquo; when you&rsquo;ve never communicated at all.</p>
<p>They&rsquo;re trained to write as if they&rsquo;re continuing an existing relationship rather than initiating a cold contact. It&rsquo;s deliberately deceptive, designed to bypass your mental filters for unsolicited sales approaches.</p>
<h3 id="identity-based-manipulation-where-it-gets-personal">Identity-Based Manipulation: Where It Gets Personal</h3>
<p>This is where sales tactics cross from annoying into genuinely offensive territory.</p>
<p>You receive a message that opens with:</p>
<ul>
<li>&ldquo;As a fellow Greek&hellip;&rdquo; (or even worse, written in Greek)</li>
<li>&ldquo;I noticed we&rsquo;re both from [your hometown]&hellip;&rdquo;</li>
<li>&ldquo;My family is also from [your region of origin]&hellip;&rdquo;</li>
<li>Comments about your name&rsquo;s etymology or ancestral background</li>
</ul>
<p>This isn&rsquo;t accidental. This is weaponizing your identity and heritage as a sales tool.</p>
<p><strong>Why is this particularly egregious</strong>: It exploits cultural norms around helping &ldquo;one of your own.&rdquo; It creates artificial obligation based on shared background. It takes something deeply personal (your heritage, your roots, your identity) and commodifies it for commercial gain.</p>
<p>Some sales trainers actually teach this as &ldquo;finding common ground&rdquo; or &ldquo;building rapport.&rdquo; But there&rsquo;s nothing genuine about it. It&rsquo;s manufacturing false kinship for the explicit purpose of making a sale.</p>
<p>For those of us from smaller ethnic communities or regions, this manipulation is especially invasive. The bonds within these communities are real and meaningful. Using them as a sales tactic is a calculated exploitation of something that is morally wrong.</p>
<h2 id="the-uncomfortable-economics">The Uncomfortable Economics</h2>
<p>Here&rsquo;s the part that&rsquo;s hard to accept: <strong>these tactics work</strong>.</p>
<p>Not on you, perhaps. You may be immune or have developed pattern recognition to understand and avoid these approaches. But they work often enough across the broader target audience to justify their continued use.</p>
<p>Those few opportunities can generate enough potential revenue to make the entire campaign &ldquo;successful.&rdquo; The remaining 99+% who were spammed or offended simply don&rsquo;t matter in this calculation, because as it turns out the ends justify the means. Let that sink in for a bit.</p>
<h3 id="who-responds-to-these-tactics">Who Responds to These Tactics?</h3>
<p>They&rsquo;re effective with:</p>
<ul>
<li><strong>Less experienced engineers</strong> who haven&rsquo;t yet developed pattern recognition for sales approaches</li>
<li><strong>People-pleasers</strong> who feel guilty not responding or uncomfortable refusing to help</li>
<li><strong>Those from cultures with strong in-group obligations</strong> (the ethnicity/ancestry play specifically targets this)</li>
<li><strong>Those who think that responding</strong> will just make it stop (personally, if I have not subscribed to a newsletter, I will not even hit unsubscribe as this is a signal for them that someone is listening)</li>
<li><strong>People who haven&rsquo;t learned to set firm boundaries</strong> with cold outreach</li>
</ul>
<h3 id="the-race-to-the-bottom">The Race to the Bottom</h3>
<p>The companies that use manipulative tactics have optimized for conversion rate, not reputation. They&rsquo;ve calculated that:</p>
<ul>
<li>Burning bridges with 99% of prospects is acceptable collateral damage</li>
<li>Offending people doesn&rsquo;t hurt them (those people weren&rsquo;t going to buy anyway, goes the logic)</li>
<li>The people who respond don&rsquo;t seem to care about the tactics used</li>
</ul>
<p>It&rsquo;s the same economic model as spam email or robocalls. A tiny success rate justifies a massive negative impression on everyone else.</p>
<p>And here&rsquo;s the meta problem: this creates a race to the bottom. Companies see competitors using aggressive tactics and getting results, so they adopt them too. Sales leaders promote SDRs who hit quota using these methods, reinforcing them as &ldquo;best practices.&rdquo; The system perpetuates itself.</p>
<h2 id="the-opportunity-cost-what-youre-not-getting">The Opportunity Cost: What You&rsquo;re Not Getting</h2>
<p>Here&rsquo;s something that makes these tactics even more frustrating: in theory, sales conversations could be valuable learning opportunities. A good salesperson understands their market deeply. They talk to dozens of engineers every week. They see patterns across companies, industries, and use cases. They could offer genuine insights about what your peers are struggling with, what solutions are actually working, and what emerging challenges you should be thinking about.</p>
<p>But that&rsquo;s not what happens with these playbook-driven approaches. The conversation is extractive, not collaborative. They&rsquo;re mining you for information (budget size, decision timeline, pain points they can exploit) while offering nothing substantive in return. The entire interaction is optimized for moving you through their sales funnel, not for mutual value exchange. Also be vigilant with what information you are allowed to share about your company.</p>
<p>This is the real tragedy of modern sales playbooks: they&rsquo;ve eliminated the possibility of unexpected learning. You might engage with a cold outreach hoping to discover something new about your market, a technology, or an approach you hadn&rsquo;t considered. Almost universally, you come away with nothing but time wasted.</p>
<p>Now imagine an interaction where someone reaches out with genuine domain expertise and offers real insights before asking for anything. Not vague numbers, but the actual facts of what the value proposition is. Would you remember them years later?</p>
<h2 id="what-this-means-for-you">What This Means for You</h2>
<p>First, recognize that you&rsquo;re the target of an industrial process. Sales development is now heavily automated, data-driven, and optimized. The personalization you see is often manufactured at scale. The commonalities they mention are frequently mined from your LinkedIn profile by automation tools.</p>
<p>Second, understand that your response (or lack thereof) is a data point in their system. They&rsquo;re iterating on what works. Every A/B test, every message variation, every new manipulation tactic exists because somewhere, at some point, it outperformed the alternative.</p>
<p>Third, accept that the vast majority of these interactions will offer you nothing of value. You&rsquo;re not missing out by ignoring them. The chance that you&rsquo;ll learn something genuinely useful is vanishingly small; not because the products are necessarily bad, but because the sales process is designed for extraction, not education.</p>
<p>Fourth, and most importantly: <strong>you owe these cold outreach attempts nothing</strong>. No response, no explanation, no referral to a colleague. The manufactured intimacy is not real. The shared heritage angle is exploitation, not community. The aggressive follow-ups after you&rsquo;ve said no are not deserving of guilt.</p>
<p>Your attention and your network are valuable. Protect them accordingly and follow up only if you are genuinely interested in their offering.</p>
<h2 id="a-better-way-forward">A Better Way Forward?</h2>
<p>For sales professionals reading this: I understand you have quotas. I understand the pressure you&rsquo;re under. But consider what you&rsquo;re optimizing for. Your help is needed to fix this broken system.</p>
<p>The manipulative tactics I&rsquo;ve described may generate short-term results, but they&rsquo;re burning long-term trust not just to your company but for all B2B outreach. They&rsquo;re training an entire generation of software engineers to automatically dismiss and block sales outreach. They&rsquo;re making it harder for every company out there to get through the noise.</p>
<p>There is a better approach: do real research, reach out with genuine specificity, respect boundaries when set, and build actual relationships rather than manufactured ones. It&rsquo;s slower. It doesn&rsquo;t scale as well. But it works with the prospects who matter most, the true potential buyers with real budgets and real problems.</p>
<p>For engineering fellows: share your experiences. When a vendor uses manipulative tactics, tell your network. When someone reaches out with genuine value and respect for your time, remember them and speak about their company. We can collectively shape what &ldquo;best practices&rdquo; look like by rewarding the behavior we want to see. And remember, those tactics are mostly executed by companies with products and services that do not speak for themselves.</p>
<p>The current system works because we allow it to. We can choose differently.</p>
]]></content:encoded></item><item><title>Full-Stack Developer 2.0</title><link>https://blog.talentlms.io/posts/full-stack-developer-2.0/</link><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><dc:creator>Vassilis Poursalidis</dc:creator><guid>https://blog.talentlms.io/posts/full-stack-developer-2.0/</guid><description>Software used to feel connected. Then it splintered. Backend, frontend, DevOps, QA. Each speaking their own language, each waiting on the others to move.
Agentic coding changes the rhythm. With AI as a second brain, developers can move across the stack, see the whole system, and keep context alive.
The result is not speed for its own sake. It is flow, coherence, and fewer walls between people who build things together.</description><enclosure url="https://blog.talentlms.io/images/posts/full-stack-developer-2.0.png" type="image/png"/><media:content url="https://blog.talentlms.io/images/posts/full-stack-developer-2.0.png" medium="image"><media:title type="plain">Abstract painted figure working across multiple screens representing full-stack development capability on a warm background</media:title><media:description type="plain">Abstract painted figure working across multiple screens representing full-stack development capability on a warm background</media:description></media:content><content:encoded><![CDATA[<img src="https://blog.talentlms.io/images/posts/full-stack-developer-2.0.png" alt="Abstract painted figure working across multiple screens representing full-stack development capability on a warm background" style="max-width: 100%; height: auto; margin-bottom: 1.5em;" /><p style="margin-bottom: 1.5em; padding: 1em; background-color: #f5f5f5; border-left: 4px solid #0066cc;"><strong>Vassilis Poursalidis</strong>, TalentLMS Engineering Director<br/>Vassilis has nearly 20 years of experience working in diverse projects in the technology industry, with a strong record of leadership and technical expertise.

He is currently an Engineering Director …</p><p>Long gone are the days when a developer will walk across the entire stack. For years, software development has been moving toward specialization. We&rsquo;ve carved ourselves into distinct tribes: infrastructure engineers who live in YAML and Terraform, architects who sketch systems in boxes and make decisions in ADRs, backend developers who speak in APIs and databases, frontend developers who breathe JavaScript and CSS, and QA engineers who guard the gates of production.</p>
<p>This specialization brought expertise. But it also brought something else: <strong>gaps</strong>.</p>
<h2 id="the-gaps-between-the-silos">The Gaps Between the Silos</h2>
<p>These gaps are where projects slow down. They&rsquo;re in the handoffs, the assumptions, the translation layers between team members and teams. They&rsquo;re in the backend developer who builds an API without considering how the frontend will consume it. The infrastructure engineer who provisions resources without understanding the application&rsquo;s actual needs. The QA engineer who writes tests disconnected from how the code actually behaves.</p>
<p>Each handoff is a game of broken telephone. Each specialized role is a potential bottleneck.</p>
<h2 id="enter-agentic-coding">Enter Agentic Coding</h2>
<p>This is where agentic coding becomes transformative, not as a way to create the mythical 10x developer, but as a way to bridge these gaps. AI-assisted development tools enable a different kind of developer: <strong>the full-stack generalist who can move fluidly across the entire stack</strong>.</p>
<p>Not because they&rsquo;re superhuman, but because they have assistance.</p>
<h2 id="the-bridge-builder-not-the-specialist">The Bridge Builder, Not the Specialist</h2>
<p>With agentic coding tools, a single developer can:</p>
<ul>
<li><strong>Provision the infrastructure layer</strong>: spinning up containers, configuring cloud resources, setting up CI/CD pipelines; not as an infrastructure specialist, but as someone who understands enough to make informed decisions with AI assistance filling the knowledge gaps.</li>
<li><strong>Design coherent architecture</strong>: mapping out system boundaries, considering scalability and maintainability, with AI helping validate approaches and spot potential issues.</li>
<li><strong>Build backend services</strong>: implementing business logic, designing database schemas, creating APIs; with AI suggesting patterns, catching edge cases, and writing boilerplate.</li>
<li><strong>Craft frontend experiences</strong>: building interfaces that actually work with the backend they just created, because there&rsquo;s no translation layer, no assumptions, just continuity.</li>
<li><strong>Think like QA</strong>: writing tests at every layer, shifting testing left (earlier in the development cycle) as much as possible, because they understand the full context of what could break and why.</li>
</ul>
<p>Importantly, these bridge builders don&rsquo;t vibe code and don&rsquo;t work in isolation. They carefully review what AI suggests and always improve how they use those tools. They&rsquo;re also supported by a network of evangelists, senior staff members, and domain experts who provide guidance, review architectural decisions, and help navigate complex technical challenges. The difference is that with AI assistance, these senior voices can focus on high-level guidance and strategic direction rather than being pulled into every implementation detail. The generalist can execute across the stack with AI handling the tactical work, while leaning on experienced colleagues for wisdom about patterns, pitfalls, and best practices that only years of experience can provide.</p>
<h2 id="the-power-of-context">The Power of Context</h2>
<p>The real advantage isn&rsquo;t speed, it&rsquo;s context. When the same person (with AI assistance) works across the stack, knowledge doesn&rsquo;t get lost in translation. The API is designed with the frontend in mind because the same person is considering both. The infrastructure is provisioned based on actual application needs, not assumptions. Tests are written by someone who intimately knows the failure modes and has incorporated the appropriate logic in the development process.</p>
<p>Agentic coding tools make this possible by:</p>
<ul>
<li><strong>Keeping you in focus</strong>: Instead of spending hours researching unfamiliar territory or waiting for a specialist to become available, developers can move forward immediately with AI assistance that understands the context of what they&rsquo;re building.</li>
<li><strong>Providing on-demand expertise</strong>: need to write a complex SQL query with proper indexing strategies? Configure a load balancer with health checks and failover logic? Set up CORS policies that balance security and functionality? The developer doesn&rsquo;t need years of specialization in databases, infrastructure, or security protocols. They need to understand the problem well enough to evaluate the solution, and AI bridges the gap between understanding and implementation.</li>
<li><strong>Maintaining consistency</strong>: when infrastructure configuration, backend logic, frontend implementation, and test suites all flow from the same contextual understanding, the result is a more coherent approach.</li>
</ul>
<p>What makes this even more powerful is that AI can leverage context from across the entire development process. When building the frontend, the AI can reference the backend implementation you just created (understanding the exact shape of API responses, the error states that might occur, and the validation rules already in place). When writing tests, it knows the actual business logic from the backend and the user interactions from the frontend. When configuring infrastructure, it understands the actual resource needs based on the application code. This contextual awareness means each layer informs and improves the rest, creating solutions that are more coherent and robust than what emerges from siloed development.</p>
<h2 id="greenfield-and-legacy-two-sides-of-the-same-coin">Greenfield and Legacy: Two Sides of the Same Coin</h2>
<p>For greenfield projects, agentic coding is remarkably effective. AI tools come equipped with knowledge of current best practices across the entire stack: modern framework patterns, security considerations, scalability approaches, testing strategies, and more. Starting from scratch, a developer with AI assistance can bootstrap a properly structured project in hours: infrastructure as code following current standards, a backend with sensible architecture, a frontend using modern patterns, and comprehensive test coverage. The AI doesn&rsquo;t just generate code; it applies the collective wisdom of thousands of well-architected projects.</p>
<p>But here&rsquo;s what makes agentic coding truly powerful: it works just as well with existing codebases. AI tools can analyze and understand the patterns, conventions, and architectural decisions already present in a project. They can be tuned to mimic the existing codebase style: the naming conventions, the error handling patterns, the testing approaches, and the architectural boundaries. When you&rsquo;re adding a new feature to a five-year-old application, the AI doesn&rsquo;t impose some idealized pattern from its training. It stays aligned with your project&rsquo;s conventions, making contributions that feel native to the codebase.</p>
<p>This adaptability means the full-stack generalist can be effective <strong>whether they&rsquo;re building something new or extending something that&rsquo;s been running in production for years</strong>.</p>
<h2 id="not-replacing-specialists">Not Replacing Specialists</h2>
<p>As mentioned earlier, experts who provide guidance remain important. Deep expertise still matters. But it&rsquo;s about recognizing that many projects don&rsquo;t need ten specialists (they need many well-rounded developers who can bridge the gaps with AI assistance).</p>
<p>For startups, small teams, and rapid prototyping, this is transformative. For larger organizations, it&rsquo;s about creating developers who can work across team boundaries, who can understand and contribute to multiple layers, who can see the whole system rather than just their piece.</p>
<p>Don&rsquo;t get me wrong, I do not advocate that we need less specialists or less developers. On the contrary, I advocate to allow specialists to do more meaningful work and to give developers new powers.</p>
<h2 id="the-skills-that-matter">The Skills That Matter</h2>
<p>The full-stack generalist in the age of agentic coding needs different skills than before:</p>
<ul>
<li><strong>Breadth over depth</strong>: understanding enough about each layer to ask the right questions, validate AI suggestions, and knowing when to call in specialists.</li>
<li><strong>Systems thinking</strong>: seeing how pieces fit together, understanding trade-offs across the stack.</li>
<li><strong>Critical evaluation</strong>: knowing when AI suggestions make sense and when they don&rsquo;t.</li>
<li><strong>Communication</strong>: being able to speak the language of different domains, even if not fluent.</li>
</ul>
<p>In a sense, the most important skills are <strong>problem-solving and the ability to communicate with fellow engineers</strong>.</p>
<h2 id="the-future-is-bridge-builders">The Future Is Bridge Builders</h2>
<p>Software development is at an inflection point. Agentic coding tools aren&rsquo;t creating 10x developers who work faster. They&rsquo;re creating bridge builders who eliminate the gaps between specialized domains.</p>
<p>This new breed of developers won&rsquo;t replace specialists. But they&rsquo;ll change how teams are structured, how quickly products can be built, and how much overhead is lost to handoffs and translation.</p>
<p>The future isn&rsquo;t about being the best backend developer or the best frontend developer. It&rsquo;s about being effective across the entire stack; not through years of specialization in each domain, but through intelligent use of AI assistance that makes generalist competence not just possible, but powerful.</p>
<p>The gaps are closing. The bridges are being built. And the developers building them might just change how we think about software development itself.</p>
<p>When you combine all of the above (the elimination of silos, the contextual awareness across layers, the ability to work on both greenfield and legacy projects, the support from senior expertise), with the significant speedup that agentic coding offers, you get compound results.</p>
<p>It&rsquo;s not just about doing more things; it&rsquo;s about doing them faster, with better integration, and with higher quality. This convergence is giving rise to what we might call <strong>v2.0</strong> of the <strong>Full-Stack Developer</strong>: someone who leverages AI to move seamlessly across the entire development lifecycle, producing cohesive solutions at a pace that would have seemed impossible just a few years ago. These developers aren&rsquo;t superhuman, but they are transformative, and <strong>they represent the future of how software gets built</strong>.</p>
]]></content:encoded></item></channel></rss>