# Herding Cats: Turning Chaos Agents into Senior Engineers (Part 1)

Congratulations. You’ve just hired the world’s fastest engineer.

They have read every single piece of documentation on the internet. They drink infinite digital espresso. They never sleep, they never complain about meetings, and they cost about $20 a month.

They also have the survival instincts of a toddler in a knife factory.

If you are using LLMs (Coding Agents) to write software, you have essentially hired a Junior Engineer with infinite energy and zero scar tissue. They haven’t been woken up at 3:00 AM because of a race condition. They haven’t spent a weekend rebasing a git history they destroyed. They don’t know *fear*.

Like any enthusiastic Junior Engineer, you wouldn’t stop them from coding—that speed is valuable!—but you also wouldn’t let them deploy to production without a very strict set of guardrails.

This series is about **Guidelines as Code**. It’s about how to take that chaotic, junior-level energy and funnel it through the lens of a Senior Architect.

In Part 1, we are tackling the most dangerous thing an Agent can do: **Managing State.**

## The Problem: LLMs are Non-Deterministic

LLMs are probabilistic. They are “vibes-based” text generators. Software, conversely, is deterministic. It is logic-based.

When you ask an LLM to write a complex `for` loop with index tracking, breaking conditions, and state mutation, you are asking a probabilistic machine to simulate a logical state machine. It usually works! Until it doesn’t. And when it breaks, it breaks in weird, hallucinated ways (like inventing array methods that don’t exist).

To fix this, we need to force the Agent to write code that minimizes state. We need **Functional Programming**.

## Rule #1: Ban the Loop

Junior engineers love loops. Loops feel like “real coding.” They involve `i++` and `break` and `continue`.

But loops are messy. They require the writer to mentally track the state of `i` and the length of the array simultaneously. Agents are bad at this. We want to force them to use **Declarative** code (what to do) over **Imperative** code (how to do it).

If you are using Cursor, Windsurf, or GitHub Copilot, you need a rule that explicitly bans the loop.

### The Guideline

Add this to your system prompt or `.cursor/rules/patterns.mdc`:

“`md
## Functional Programming & Iteration
– **Do NOT use `for` loops** – use `map`, `filter`, `forEach`…
– **Do NOT use `while` loops** – refactor to use functional…
– **Prefer lodash/underscore** methods when complex object…
“`

### The Result

Without this rule, an Agent might write this “Junior” code:

“`js
// The “Junior” Agent Way
// It works, but it’s mutating state and managing indices.
let activeUsers = [];
for (let i = 0; i < users.length; i++) { if (users[i].isActive) { // Did the agent check if users[i] exists? Maybe. activeUsers.push(users[i]); } } ``` With the rule applied, the Agent is forced to write this: ```js // The "Senior" Agent Way // No index variables. No external state mutation. const activeUsers = users.filter(user => user.isActive);
“`

The second example leaves no room for an “off-by-one” error. It is statistically harder for the LLM to mess this up.

## Rule #2: The “Anti-Clever” Clause

Once you force an Agent to use Functional Programming, they often get *too* excited. They discover `Array.reduce`.

`reduce` is the Swiss Army Knife of array methods. You *can* use it to map, filter, and summarize all at once. And because LLMs want to be efficient, they will try to cram an entire ETL pipeline into a single `reduce` function. This creates code that is “clever” but impossible to read.

We need to force the Agent to prioritize **Readability over Brevity**.

### The Guideline

“`md
## Readability & Complexity
– **Prefer Multiple Operations Over Complex Reduces**
– Use multiple `map`, `filter`, etc. operations instead…
– Example: `data.map(x => x.id).filter(id => id > 0)` is…
– Favor readability over brevity – clear code is better…
“`

### The Result

Without this rule, the Agent tries to show off:

“`js
// The “Look how smart I am” Agent
const result = rawData.reduce((acc, curr) => {
if (curr.status === ‘active’) {
// Doing two things at once: filtering AND transformation
acc.push({ id: curr.id, name: curr.name.toUpperCase() });
}
return acc;
}, []);
“`

With the rule, the Agent writes code that tells a story:

“`js
// The “Maintainable” Agent
const result = rawData
.filter(item => item.status === ‘active’) // Step 1: Filter
.map(item => ({ // Step 2: Transform
id: item.id,
name: item.name.toUpperCase()
}));
“`

## Rule #3: Immutability is Law

The most common bug introduced by AI Agents is the **Side Effect**.

Agents treat functions like buckets where they can dump any logic they want. They will happily modify a global variable inside a helper function, or mutate the arguments passed to them. This creates “spooky action at a distance” where changing a function in one file breaks a feature in another.

We have to handcuff them.

### The Guideline

“`md
## Side Effects
– **Minimize Side Effects**: Avoid modifying variables outside…
– **Avoid mutating input parameters**: Prefer immutable operations.
– **Pure Functions**: Prefer functions that return new values…
“`

### The Result

**Bad Agent Behavior:**

“`js
function updateUserScore(user) {
// Mutating the input object directly.
// If ‘user’ is used elsewhere, it just changed silently.
user.score += 10;
return user;
}
“`

**Good Agent Behavior:**

“`js
function updateUserScore(user) {
// Returns a NEW object. The original ‘user’ remains untouched.
return {
…user,
score: user.score + 10
};
}
“`

## Summary: Constrain the Chaos

When you are prompting an LLM for code, do not ask it to “write a function that does X.” That is how you get spaghetti code.

You must ask it to “write a function that does X, adhering to Y principles.”

By enforcing **Functional Methods**, **Immutability**, and **Separation of Concerns**, you are effectively removing the “Junior” tendencies from the AI. You are forcing it to code with the discipline of a Senior Engineer who knows that the most clever code is usually the hardest to debug.

In [Part 2:](/herding-cats-turning-chaos-agents-into-senior-engineers-part-2) We tackle the Agent’s optimism. We’ll look at **Control Flow and Async patterns**—how to force the Agent to stop assuming APIs will always work and databases will always be fast.

Author:
[Justin Morant](https://www.linkedin.com/in/justinmorant/)

Leave a Reply