Four months ago, I wasn't planning to build an automation platform. I was just trying to eliminate a few tedious, repetitive business tasks.
Like many internal tools, it started innocently with a single Python script. It solved one problem perfectly, so I wrote another. Then another. Before long, a fleet of twenty independent scripts was handling different stages of our core offer processing pipeline.
On paper, the architecture was elegant in its simplicity:
- Python scripts executed the core business logic.
- Google Sheets served as both the database and the configuration engine.
- Operations team members updated spreadsheets whenever a workflow needed to change.
For a small team of five, it felt like a brilliant, low-overhead solution. It worked flawlessly.
Until the spreadsheets stopped just storing data and became the codebase itself.
The Invisible Runtime
None of our non-technical team members wrote a line of Python, yet they were programming the system every single day.
Routing rules lived inside spreadsheet cells. Complex workflow behaviors depended entirely on free-text strings. Script parameters were modified manually on the fly. In this setup, changing a single value didn't just alter data, it fundamentally changed how the underlying software executed.
Google Sheets had quietly mutated into our primary programming language.
The problem is that spreadsheets don’t provide any of the guardrails that make programming languages reliable. We had:
- Zero validation to catch syntax errors.
- Zero type safety to ensure integers weren't replaced by strings.
- Zero access control to prevent catastrophic overwrites.
- Zero code review processes or version control history.
A simple typo was no longer just a cosmetic error. It was a broken, unverified production deployment.
The Hidden Cost of "Flexibility"
At first, treating spreadsheets as a configuration layer felt incredibly empowering. Anyone on the operations team could pivot a workflow instantly without waiting on a developer to modify Python code.
But we quickly discovered that every ounce of flexibility came with a massive tax of hidden complexity.
As our operations grew, the same Python script often needed to be reused across multiple workflows, each requiring slightly different parameters. Google Sheets simply wasn’t built to handle this type of polymorphic reuse. Script parameters ended up scattered across disconnected tabs, and keeping them synchronized became a logistical nightmare.
Eventually, duplicating an entire script and hardcoding the changes became easier than fighting our spreadsheet configuration model. Our script count exploded, not because our business logic was changing, but because our configuration layer lacked the expressiveness to handle variations safely.
The bottleneck wasn’t Python. The bottleneck was that our configuration model had buckled under its own weight.
Spreadsheets Don't Scale as Architecture
As the system expanded, our configuration state fragmented completely. It was no longer one sheet; it was spread across fifteen distinct Google Sheets.
[Google Sheet 1] ──┐
[Google Sheet 2] ├───> [Fragile Python Scripts] ───> Production Risk
[Google Sheet 3] ──┘
Some sheets tracked operational data. Others documented manual workflows. Others held environment configurations. Some tried to do all three at once.
With nothing enforcing schema consistency between them, everything began to drift. Documentation drifted from reality, configurations drifted between environments, and operational boundaries blurred. Because everyone had edit access to everything, an accidental cell deletion, a renamed column, or an unexpected data type would silently cripple an upstream workflow.
Worse yet, these failures rarely happened immediately. They would lie dormant, surfacing hours later in downstream processing, making root-cause debugging an exhausting exercise in forensics.
The Concurrency Ceiling
As transaction volumes grew, we hit a fundamental architectural wall: parallel execution was practically impossible.
Because the entire ecosystem depended on shared, stateful spreadsheets, running multiple scripts concurrently immediately introduced severe race conditions. Two processes writing to or reading from the same sheet at the same time yielded completely unpredictable behavior.
We didn't avoid parallel processing because our workload didn't need it. We avoided it because our data layer couldn't handle it safely. The architecture itself was actively discouraging our system growth.
The Realization
For months, I fell into the classic developer trap. I thought the answer was just writing better scripts—cleaner code, tighter abstractions, more robust helper functions.
But every optimization I made in the codebase only pushed the bottleneck further into the spreadsheets. The Python code became pristine, while the configuration grew more volatile.
Then came the moment of clarity that completely redirected the project. I looked at my week and realized I was no longer spending time writing features or business logic. I was spending 80% of my time managing and debugging configuration.
That realization forced me to stop asking:
How can I write better automation scripts?"
And start asking:
Why am I treating system configuration as an afterthought?
That shift in perspective changed everything. It marked the end of writing disconnected scripts, and the beginning of building a dedicated platform. A system whose primary job isn’t just executing code—but making the configuration driving that code safe, verifiable, and scalable.
In my next post, I’ll dive into why configuration deserves to be treated with the exact same rigor as application code, and how that philosophy changes the way we should build internal tools from day one.