The Five Pillars: Leadership for Effective Custom Software

The cover of The Five Pillars: Leadership for Effective Custom Software, by Jeffrey Palermo

The Five Pillars: Leadership for Effective Custom Software is a book by Jeffrey Palermo.

From the publisher's description, as the audiobook's listing at Audible gives it:

Offering a critical guide for CEOs, CTOs, and all stakeholders weary of software failures costing their companies dearly, Jeffrey Palermo uses his book to address the pervasive issues in software development that threaten business continuity and growth. This five-step process to ensure software success starts with a stark reminder of how poor software can cripple even the largest enterprises. Jeffrey draws from extensive industry experience, spanning Fortune 100s to startups, unveiling the pitfalls of ineffective software, and the practical solutions needed to avoid them.

The five pillars, as Clear Measure's resource page for the book names them:

  1. Create Clarity
  2. Establish Quality
  3. Achieve Stability
  4. Increase Speed
  5. Optimize the Team

Where to get it

AI‑Driven DevOps Architecture

The futurists are saying that AI is going to write all the code and do everything automagically. But that’s just pie in the sky.

At the same time, right now we are using AI coding tools directly in front of us to eliminate endless Google searches—figuring out how a certain API needs to be used, what the syntax is, how to use Library A for that, or whatever it happens to be.

I don’t have a great memory for all the PowerShell commands, so AI really helps me determine the right usage on the command line.

I want to talk about the notion of an AI Software Factory. You’ll probably see a Forbes headline someday with that phrase, but I want to view it through a software engineering lens. The pattern I think is most appropriate—the one I want to talk about—is the AI‑driven DevOps architecture.

Back in 2008 and 2009, I had a major epiphany and published thoughts on Onion Architecture and managing dependencies in .NET applications. The Onion Architecture : part 1 | Programming with Palermo. In 2019, I wrote my fourth book, .NET DevOps for Azure, which put forth an architecture for DevOps environments in .NET applications.

Building on that foundation, I'd like to explore what it means to have an AI‑Driven DevOps Architecture.


What Must an AI‑Driven DevOps Environment Include?

If all I do is use Copilot inside Visual Studio, or tools like Claude CLI, Cursor, OpenCode, or Junie inside JetBrains Rider—essentially the engineer‑attended coding tools—then my DevOps environment doesn’t need major changes.

Why? Because I’m still reviewing every line of code. The tools move only as fast as my brain. This brings huge benefits—fewer Google searches and less documentation‑digging—but nothing architecturally must change in the DevOps environment.

But that won’t get us 10x or 100x faster software delivery. That’s just incremental improvement. That’s just making individual engineers faster.


A Concrete Example

Imagine I need to extend a data field from 10 characters to 12.

This is simple. We know exactly how to do it:

  • Database migration
  • Automated tests
  • Validation
  • UI adjustments

Easy. So what needs to exist in the DevOps environment in order to delegate this entire change to a computer?

This is where the AI‑Driven DevOps Architecture begins.


The Automation Requirements

Let’s assume we define an issue and assign it to a Copilot Coding Agent. In that case, the DevOps environment must support all of the following fully automated:

1. Feature Specification

Detailed enough for any team member—or an AI agent—to pick up.

2. Automated Task Breakdown

Humans usually break down features into tasks. Now it must be automated.

3. Repo and Environment Setup

Cloning the Git repository and setting up the development environment must be 100% automated.

4. Feature Branch Creation

Automatically created.

5. Private Build Verification

Automatically run a private build to ensure a clean environment.

6. Code + Config Changes

All changes generated automatically.

7. Pre‑Commit Build

Run automatically.

8. Push to GitHub

Automated.

9. Integration Build

Run automatically on the feature branch.

10. Release Candidate Packaging

Automatically produced.

11. Test Environment Deployment

Automated deployment to the first‑line test automation environment (the TDD environment).

12. Full System Acceptance Testing

Run automatically.

13. Pull Request Creation

Created automatically.

14. Pull Request Review

Reviewed automatically.

15. Merge

Merged automatically.

16. Master Branch Build

Automatically triggered.

. . . and so forth.


The Shift to AI‑Driven DevOps Architecture

Every non-creative step humans normally do manually must become automated.

These enhancements transform a traditional DevOps environment into an AI‑driven DevOps architecture—one that enables completely automated software enhancements, producing:

  • A completed build
  • A deployed version in a manual test environment
  • Something product management can evaluate
  • A candidate ready for release to customers

This is the next frontier in DevOps.

Originally published on LinkedIn.

The Mindset Shift Required for AI‑Driven Development

For decades, software engineering has operated under a deeply ingrained mental model: developers design the system, write the code, understand its internals, and remain responsible for operating and maintaining it. Even with all our talk about “raising the level of abstraction,” we have mostly continued hand‑crafting software the way mechanics hand‑build custom cars.

But today we’re standing at the edge of a transformational shift—one that requires us to rethink who maintains software, who changes it, and how those changes safely enter production. AI‑driven development is forcing us to adopt a new mindset, and to get there, we need better analogies to understand what’s changing.

From Mechanic‑Built Cars to Mass‑Produced Vehicles

When I was a kid, I built and repaired my own bicycle. I knew every bolt, every bearing, and every cable. I became a pretty good bike mechanic. I had an uncle who took that same spirit into the world of automobiles. He built his own car from a Cobra kit. He could assemble the frame, drop in a Ford Mustang engine, and wire everything end‑to‑end. It was his machine—designed by him, built by him, operated by him.

For a long time, software has worked exactly like that.

We have been the designers, the builders, and the operators of our own “custom‑built cars.” Even when we deliver software to users, the reality is: we’re the ones who turn it off and on. The users operate workflows, but developers operate the system.

But think about the difference between a mechanic‑built kit car and a Toyota. Toyota builds cars so that anyone can operate and maintain them—without being a mechanic. Owners can perform basic servicing and routine maintenance without knowing how an engine works.

This is exactly the shift software must make.

Software Must Become Maintainable by Non‑Programmers

In the world of AI‑driven development, our users will need the ability to service the software, not just use it. Maintenance won’t just mean restarting a service or changing a setting—it will include modifying configuration and, yes, even making source‑level changes.

That sentence still makes many engineers uncomfortable.

But once we accept that:

not all changes must be performed by a fully‑skilled software engineer,

then the DevOps environment itself must evolve. AI coding tools become the intermediary—allowing analysts, product owners, support personnel, and other trained team members to perform controlled, validated, safe changes.

Imagine a business analyst safely adding a value to a dropdown—without pulling a developer off higher‑value engineering work. In an AI‑augmented environment, this is not only realistic; it’s essential.

Why DevOps Must Evolve Beyond Handcrafted Code

For years, we’ve invested heavily in DevOps maturity:

  • Private builds with unit and integration tests
  • Integration builds producing deployment-ready artifacts
  • Full system acceptance tests in deployed environments
  • Pull requests with human review
  • Release automation and continuous delivery pipelines

This foundation remains crucial—but insufficient.

Today, a pull request from a simple dropdown modification still requires a developer to review it. That means:

  • A human becomes the bottleneck
  • Developer attention becomes the scarce resource
  • Productivity gains plateau

If every AI-generated change still requires a developer to review the PR, then the industry will never achieve the 10x productivity improvement that AI makes possible.

We need additional automated checks—new validation techniques—to determine:

Is a feature branch stable? Did this change unintentionally break anything?

If automated systems can answer that confidently, then humans no longer need to review every simple change.

Identifying Which Changes Should Be AI‑Driven

Not all software work is the same. Some work is:

  • New architectural patterns
  • Novel paradigms
  • First‑time implementations
  • Complex engineering decisions

These still require expert developers hand‑crafting code.

But other work—the vast majority of day‑to‑day changes—comes down to repeating well-established patterns. These repetitive, low-risk modifications are the perfect entry point for AI-driven development.

Once a pattern exists, repeating it is not engineering—it's manufacturing.

AI excels at manufacturing.

Where This Transformation Leads

The challenge I’m proposing is simple but profound:

What must we add to our DevOps environments to let non‑programmers safely perform routine maintenance—including changes that touch source code?

Your DevOps pipeline must evolve to:

  • Validate AI-generated code automatically
  • Confirm functional stability with higher test coverage or new test methods
  • Detect unintended side effects reliably
  • Provide automated PR approvals for safe classes of changes
  • Allow analysts and operators to perform controlled maintenance work

The goal is not to remove software engineers—it’s to free them.

If developers are still reviewing every tiny change, we cannot scale. If they are designing, validating, and engineering the core patterns—and AI handles the repetition—then the industry finally moves toward true software engineering maturity.

A Call for a New Mindset

As AI coding tools advance, the opportunity becomes clearer:

  • Software engineers focus on engineering
  • AI handles pattern-based manufacturing of code
  • Non-programmers, empowered by AI, handle routine maintenance
  • DevOps systems ensure everything is safe, stable, and validated

This is the mindset shift required for the next era of software.

Now the question is: What changes will you make to your DevOps environment to unlock this new model?

God bless.

Originally published on LinkedIn.

.NET DevOps for Azure

I’ve been working hard to bring together all that I have learned over the past years into my new book: .NET DevOps for Azure.

It is a culmination of a long-time vision, some key leadership, and a confluence of industry events.

Almost fifteen years ago, the I gained a passion for helping .Net DevOps engineers and DevOps services companies succeed, for making the complex simple, and for finding rules of thumb that would work for 80% of situations. With too many options in the software world and too many answers of “it depends”, the industry has been starved for the ability to do something “by the book.”

This book presents a scenario where a .NET developer can say “I’m doing Azure and .Net DevOps by the book.” In this manner, one would know what models and patterns were in play and what to expect from said environment.

The examples largely use Visual Studio 2019 preview edition. However, the code and the Azure DevOps Services pipeline function with .NET Core 2.2 and can be used to implement applications.

The example configuration used throughout this book can be leveraged through a public project and source code repository online.

Visit Amazon to order. [Click Here]

Or email me jeffrey@clear-measure.com to get a free eCopy of Chapter 3: The Professional-Grade DevOps Environment!

.NET DevOps Bootcamp

Architect + Lead Engineer Hands-On 2-Day Immersion hosted by ME! Jeffrey Palermo!

Is simplifying your software development and processes something that you’d like to see happen in your organization?
If so, this class was made with you and your team in mind!   

Join me!
  .NET DevOps Bootcamp: Architect & Lead Engineer Hands-On Immersion 
Hosted by Jeffrey Palermo
January 16th-17th
Austin, TX

I’m excited to be able to offer this 2-Day training. Walking you through the simple 7 key steps to simplify your .NET DevOps world where I simplify the development and deployment process – making it applicable to your every day.   

Attendees will learn concepts, apply the learning and also implement the latest DevOps tools for Microsoft-based applications, including Azure DevOps Services, Git, Azure Pipelines, Azure PaaS environments, and Octopus Deploy.  

You’ll also get:

My current favorite private build script

I do pull in the Exec function from psake just because it was coded very well.  This build script is just powershell and is geared for .Net Core

. .\BuildFunctions.ps1

$projectName = “OnionDevOpsArchitecture”

$base_dir = resolve-path
.\

$source_dir =
“$base_dir\src”

$unitTestProjectPath =
“$source_dir\UnitTests”

$integrationTestProjectPath =
“$source_dir\IntegrationTests”

$uiProjectPath =
“$source_dir\UI”

$databaseProjectPath =
“$source_dir\Database”

$projectConfig = $env:BuildConfiguration

$version = $env:Version

$verbosity = “q”

$build_dir =
“$base_dir\build”

$test_dir =
“$build_dir\test”

$aliaSql =
“$source_dir\Database\scripts\AliaSql.exe”

$databaseAction = $env:DatabaseAction

if
([string]::IsNullOrEmpty($databaseAction))
{ $databaseAction = “Rebuild”}

$databaseName = $env:DatabaseName

if
([string]::IsNullOrEmpty($databaseName))
{ $databaseName =
$projectName}

$databaseServer = $env:DatabaseServer

if
([string]::IsNullOrEmpty($databaseServer))
{ $databaseServer =
“localhost\SQL2017”}

$databaseScripts =
“$source_dir\Database\scripts”

if
([string]::IsNullOrEmpty($version))
{ $version = “9.9.9”}

if
([string]::IsNullOrEmpty($projectConfig))
{$projectConfig = “Release”}

Function Init {

    rd
$build_dir -recurse -force  -ErrorAction Ignore

       md $build_dir
> $null

       exec {

              &
dotnet clean
$source_dir\$projectName.sln
-nologo -v $verbosity

              }

       exec {

              &
dotnet restore
$source_dir\$projectName.sln
-nologo –interactive -v
$verbosity 

              }

    #Write-Host
$projectConfig

    #Write-Host
$version

}

Function Compile{

       exec {

              &
dotnet build
$source_dir\$projectName.sln
-nologo –no-restore -v
$verbosity -maxcpucount –configuration
$projectConfig –no-incremental
/p:Version=$version /p:Authors=”Clear
Measure”
/p:Product=”Onion
DevOps Architecture”

       }

}

Function UnitTests{

       Push-Location -Path
$unitTestProjectPath

       try {

              exec {

                     &
dotnet test -nologo -v
$verbosity –logger:trx
–results-directory $test_dir –no-build
–no-restore –configuration $projectConfig

              }

       }

       finally {

              Pop-Location

       }

}

Function IntegrationTest{

       Push-Location -Path
$integrationTestProjectPath

       try {

              exec {

                     &
dotnet test -nologo -v
$verbosity –logger:trx
–results-directory $test_dir –no-build
–no-restore –configuration $projectConfig

              }

       }

       finally {

              Pop-Location

       }

}

Function MigrateDatabaseLocal {

       exec{

              &
$aliaSql $databaseAction $databaseServer
$databaseName $databaseScripts

       }

}

Function MigrateDatabaseRemote{

       $appConfig =
“$integrationTestProjectPath\app.config”

    $injectedConnectionString =
“Server=tcp:$databaseServer,1433;Initial
Catalog=
$databaseName;Persist Security
Info=False;User
ID=
$env:DatabaseUser;Password=$env:DatabasePassword;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection
Timeout=30;”

       write-host “Using connection string:
$injectedConnectionString“

    if (
Test-Path “$appConfig“ )
{

        poke-xml
$appConfig “//add[@key=’ConnectionString’]/@value”
$injectedConnectionString

    }

       exec {

              &
$aliaSql $databaseAction $databaseServer
$databaseName $databaseScripts
$env:DatabaseUser $env:DatabasePassword

       }

}

Function Pack{

       Write-Output “Packaging
nuget packages”

       exec{

              &
.\tools\octopack\Octo.exe pack –id
“$projectName.UI” –version
$version –basePath $uiProjectPath
–outFolder $build_dir

       }

       exec{

              &
.\tools\octopack\Octo.exe pack –id
“$projectName.Database”
–version $version –basePath
$databaseProjectPath –outFolder $build_dir

       }

       exec{

              &
.\tools\octopack\Octo.exe pack –id
“$projectName.IntegrationTests”
–version $version –basePath
$integrationTestProjectPath –outFolder
$build_dir

       }

}

Function PrivateBuild{

       Init

       Compile

       UnitTests

       MigrateDatabaseLocal

       IntegrationTest

}

Function CIBuild{

       Init

       MigrateDatabaseRemote

       Compile

       UnitTests

       IntegrationTest

       Pack

}

Palermo Pamphlet 002 – State machine design


Download this episode from the Internet Archive (MP4, 7:31, 113.1 MB). Its first host no longer has it.

In this episode, I go over how to design a workflow feature for an entity in a software application. It’s important to separate states from state transitions.

Here are the show notes

Palermo Pamphlet launch – episode 001


Download this episode (MP4, 4:41, 76.3 MB)

My goal is to teach, inform, and have a little fun. But I want this to provide value for programmers shipping custom software using Microsoft tools. Here is the first episode of the Palermo Pamphlet, which I hope will be a valuable resource to you.

Here are the show notes

Performance tuning an Azure DevOps build configuration

We’ve all seen the culprits that constantly add time to builds.  One might observe that your NPM install or Nuget restore can take several minutes.  I remember back to the times of CC.Net in 2005 when a small application build could happen in 45 seconds, including unit tests.  And 10 minutes as a “thou shall not go over this” threshold.  So we cannot allow NPM or any other step to take minutes.  We have to ferret that out.
The answer is the same as code performance profiling.  Find out where every build is spending the same time doing work that adds no value or doesn’t vary often. Then we cache the result.  For so many builds, these are the culprits that take time but typically aren’t the changes that are being tested from build to build:
  1. Obtaining a build server (when choosing hosted build agents)
  2. Cloning the source
  3. Package restores
  4. Copying/archiving build artifacts
Here are my common solutions for reducing these common culprits (I’d be interested to know how others have eliminated these time sucks)
  1. Use our own Azure VMs as the build agents (running multiple agents on a single VM) – always available at a moments notice
  2. Let Azure Pipelines be a little less aggressive with cleaning source and instead have the build script delete the build directories at the beginning – removes need for a full clone and can just be a pull (works most of the time and requires probably a monthly purge for a clean clone, but saves SOOO much time)
  3. a) retain cloned working tree so that the previous package restore is used for subsequent builds or b) check in packages so that package restores are not necessary for every build
  4. Once builds are working and reliable, only archive the build artifacts that are directly used by the release pipeline (typically the nuget packages that house the application components)