# Chapter 2 · Vibe coding as a squad

Machine edition of https://agentc.consulting/agent-enhanced-development/chapter-2/vibe-coding-as-a-squad
Part of Agent-Enhanced Development: https://agentc.consulting/agent-enhanced-development.md
Source repository: https://github.com/AgentC-Consulting/aed-conventions/blob/main/README.md

## The moment

A teammate's agent renamed what mine had just named. Two of us and two agents
were in one billing file, and it held `days_late` and `how_many_days_late`, a
`process` that buried the plan and a `perform` that read like one. The same
intent in two vocabularies, and a diff nobody could review. This chapter is about
the squad, not yet about the rules themselves; chapter 3 teaches naming one rule at
a time. Nothing here rewrites anybody's work. We pick one convention, and the file
agrees with itself again.

## Two cursors: one billing file, four hands

1. Two drafts, in the order they landed. My agent's draft went in first, a
   teammate's agent's draft landed second, and neither of them was wrong. Both are
   `src/billing/apply_late_fee.cr`. Both apply a late fee. They drifted because
   nothing told either agent which words this repository uses.
2. The conflicts, named. Four of them are in this file, and every line of every one
   is marked in both panes. **The rename**: `days_late` against `how_many_days_late`.
   **The buried plan**: a `process` with an `if` wrapped around three inlined
   operations, against a `perform` that reads like a plan, a `return if` guard and
   one named step per line. **The magic numbers**: `grace_period_in_days` and
   `late_fee_rate` declared as properties with defaults, against a bare `30` and
   `0.05` sitting inside the method bodies, which is the one conflict that takes two
   lines on each side. **The mailer**: `CustomerMail` against `CustomerMailer`. A
   fifth conflict is not in this file at all: in the `Customer` both agents read
   from, `name` against `first_name` and `subscriptions` against
   `list_of_all_active_subscriptions`.
3. You pick the convention. One name per idea. The plan lives in `perform`, one
   named step per line. The numbers stay properties with types and defaults, so a
   requirement carries its policy. The why goes in a comment, and the code says the
   what. Watch the marks: the lines the convention keeps stay lit, the lines it drops
   go struck through, and both panes lose some. You are not choosing whose code wins,
   you are choosing which words the repository speaks. The convention keeps the
   second draft's `how_many_days_late` and its `perform` with the guard, and the
   first draft's `grace_period_in_days`, `late_fee_rate` and spelling of the mailer.
4. The panes converge and the diff reads. Both tabs now show the same file, and it
   is neither draft: the second draft's `how_many_days_late` and its `perform`, the
   first draft's `grace_period_in_days`, `late_fee_rate`, and spelling of the
   mailer. Either
   agent, handed the same rules, writes this file. What is left in the diff is the
   change itself, which is the only thing a reviewer ever wanted to see.

Vibe coding works alone because the vocabulary lives in your head, and you are the
only one drawing on it. Team vibe coding has four heads in the file, two people
and two agents, and none of them can read the other three. The vocabulary has to
come out of your head and onto paper before it can be shared, and that is the
whole move this chapter asks for.

My agent's pane, the draft that went in first:

```crystal
class Billing::ApplyLateFee
  property invoice : Invoice
  property days_late : Int32 = 0
  property grace_period_in_days : Int32 = 30
  property late_fee_rate : Float64 = 0.05

  def process
    if days_late >= grace_period_in_days
      fee = invoice.amount * late_fee_rate
      invoice.charge(fee)
      CustomerMail.late_fee(invoice).deliver
    end
  end
end
```

A teammate's agent's pane, the draft that landed second:

```crystal
class Billing::ApplyLateFee
  property invoice : Invoice
  property how_many_days_late : Int32 = 0

  def perform
    return if how_many_days_late < 30
    charge_the_late_fee
    tell_the_customer
  end

  private def charge_the_late_fee
    invoice.charge(invoice.amount * 0.05, kind: :late_fee)
  end

  private def tell_the_customer
    CustomerMailer.late_fee(invoice).deliver
  end
end
```

The file both panes show once the convention is picked:

```crystal
# Applies a late fee to an invoice that has gone past its grace period, then tells the customer.
class Billing::ApplyLateFee
  # -- What this needs --

  # The invoice being charged. It knows its customer, its amount, and its due date.
  property invoice : Invoice

  # How many days past the due date the invoice is today.
  property how_many_days_late : Int32 = 0

  # -- Company policy --

  # Days past due before any fee applies.
  property grace_period_in_days : Int32 = 30

  # The fee, as a share of the amount owed.
  property late_fee_rate : Float64 = 0.05

  # -- How it runs --

  def perform
    stop_unless_the_invoice_is_overdue
    charge_the_late_fee
    tell_the_customer
  end

  # Nothing happens inside the grace period.
  private def stop_unless_the_invoice_is_overdue
    return if how_many_days_late < grace_period_in_days
  end

  # The fee is a share of the amount owed, charged to the same invoice.
  private def charge_the_late_fee
    invoice.charge(invoice.amount * late_fee_rate, kind: :late_fee)
  end

  # The customer gets the notice by email.
  private def tell_the_customer
    CustomerMail.late_fee(invoice).deliver
  end
end
```

That file is neither draft. It keeps the rename, the `perform` and the guard clause
from the draft that landed second, and the named policy properties and the mailer's
spelling from the one that went in first, which is what picking a convention rather
than a winner looks like. It is
also chapter 3's file, arrived at from the other direction: chapter 3 reads one file
four ways, and this chapter is two people arriving at the same file from two
keyboards. The fifth conflict, the `Customer` model both agents read from, is
settled below in the before and after the conventions publish.

## Who is in the file

- **You.** You opened the file first, so your agent's words went in first. That is
  an accident of order, not a claim, and it is the weakest possible basis for a
  house style.
- **The other developer.** They have to review a diff where half the lines are a
  rename neither of you asked for and neither of you can evaluate. Renames hide
  changes, which is why a squad that has not agreed on names reviews badly even when
  everyone is careful.
- **Your agent.** It read the file as it stood, built a theory of what the code means
  from the names it found, and wrote the first draft against that theory.
- **A teammate's agent.** It read the same file once that draft had landed, found
  names that carry no agreed meaning, and reached a different theory, because the
  file it read said two things.
- **The owner.** Somebody who does not read Crystal has to trust that the late fee
  is charged when the policy says it is. They read the file the way chapter 3 shows:
  what it needs, the policy, how it runs. Two vocabularies produce no such page.

The convention is the thing that lets everybody's agents get up to speed on the
intended context right away. AED is a strategy for improving collaboration between
developers plus agents and other developers plus their agents; squad vibe coding is
the practice of having one convention rather than four.

## The rules this chapter settles

This chapter settles that the rules are shared and where they live. Chapter 3 teaches
naming in depth and chapter 4 teaches process managers, so what follows is the
agreement itself, stated the way the conventions state it.

- The guiding rule, and the one a squad has to agree on before any of the others
  matter: "Prefer the form that reads like a plain statement of intent. Reach for
  shorthand only when it makes the intent _clearer_, never just shorter."
- "Data models should be singular, and only concerned with their own individual
  behavior." Good: `Customer`. Bad: `Customers`.
- "Classes should be name spaced according to the feature that is being implemented."
  Good: `Billing::ActivateNewCustomerSubscription`. Bad: `NewCustomerSubscription`.
  In this book: `Billing::ApplyLateFee`.
- "Class names should be short statements or phrases that clearly express the process
  being performed."
- "Class attributes of non-enumerable primitive types should be phrased as short
  statements for what the intended purpose of the attribute is." `first_name`, never
  `name`. This is the rule that settles the rename in the scene.
- "Class attributes of enumerable (Array or Array-like) objects or types should be
  phrased with `list_of_` or `collection_of_` or `array_of_`."
  `list_of_previous_orders`, never `orders`.
- "Class attributes for boolean types should be named as if the expression is a
  question beginning with `if`." Good: `has_a_valid_payment_method`. Bad:
  `payment_method_present`.
- "Class attributes of non-primitive types should be named using short statements or
  phrases that accurately and clearly express how that attribute is to be used."
  Ideal: `currently_active_subscription`. Bad: `subscription`.
- "Method names should be phrases or statements that explain the process thats taking
  place." And, from the plugin's own adoption text: "Process managers are named from
  a 'when'-statement and expose a single `perform` entry point that reads like
  pseudocode." That settles the second conflict: the plan belongs in `perform`, one
  named step per line, not buried in a method body. Chapter 4 builds the rest.
- Rule 4 of the edit-level rules: "Use full, intention-revealing names." "Avoid
  `do_it`, `tmp`, `x`, `res2`. A good name removes the need for a comment."
- Rule 5: "Say *why* in a comment, let the code say *what*." "If a comment could be
  deleted with no loss because the code already says it, delete it."
- Rule 6: "One statement per line; let the formatter own the layout." "Canonical
  formatting means every reader and every diff sees the same shape." That is what
  makes a diff written by two agents reviewable at all.

## Why it matters to the agent

An agent reading your file is not compiling it. It is doing something closer to what
you do: building a working theory of intent from names, shapes, and comments, then
editing against that theory. Two vocabularies in one file means two theories, and the
second agent's plausible-looking edit is the one that quietly breaks the first
agent's promise. A name like `how_many_days_late` is not style; it is a load-bearing
claim about intent that an agent can anchor an edit to.

The conventions state this as the reason they are published at all: agents "don't
just read your code," they "read your conventions, when you give them any." A rules
document
with before-and-after examples and a checklist is close to the most agent-legible
artifact you can put in a repository.

The repository publishes two pieces of evidence for this and states the limits of
both.
In a snippet-scale comprehension benchmark (2026-07-07), Claude Haiku answered
intent, modification, and defect probes about ten pairs of Crystal snippets: the
AED-style variants scored 60 of 60 and the conventional compressed variants 54 of 60,
with the whole gap in the defect-finding and intent probes and the modification
probes tied. One run, one small model, ten pairs: the README calls that a
directional signal, not proof. In a codebase-scale build-off (2026-08-11), four headless Haiku
runs built the same pet-tracker feature in the same Crystal template, two of them in
a repository carrying the AED section in its CLAUDE.md and the advisory naming hook,
two without. All four type-checked green. The two without the conventions finished
zero of five working user journeys; the two with them finished two and four. Two runs
per arm, one task, one model: the README calls that a demonstration, not a study.

Note what the build-off also shows: every run shipped on the compiler's word and none
of them ran a write path. Clarity is half of AED. The other half is mechanical, and
it is the half a squad feels fastest: type-check at edit time, not at build time. In
Crystal that is `crystal build --no-codegen`, which runs in seconds, so a wrong
keyword or an undefined method surfaces while the agent still has the context to fix
it instead of twenty edits later. The conventions keep the code clear; the edit-time
check catches an agent's confident nonsense.

## Before and after

The fifth conflict, settled: the one that is not in the billing file at all. This is
the `Customer` both agents read from, exactly as the conventions state it: one class
instead of two spellings of one class.

```crystal
# before
class Customer
  property name : String
  property email : String
  property subscriptions : Array(Subscription) = [] of Subscription
end

# after
class Customer
  property first_name : String
  property last_name : String
  property email_address : String
  property list_of_all_active_subscriptions : Array(Subscription) = [] of Subscription
  property is_this_an_enterprise_customer : Bool = false
end
```

The "after" is not more correct Crystal. It is the version both agents produce
without being asked twice, which is the only property that matters when four hands
are in the file.

## Where the convention lives

A convention that lives in one person's head is not shared, and one that lives in a
chat thread is not read. The conventions' own answer: "The rules live in one canonical,
versioned place," published, signed, and tagged, and "Pin to the tag, not to `main`."
For collaborative vibe coding the last mile is shorter than it looks: run `/aed:adopt`
and the plugin writes an `## AED conventions` section into the project's CLAUDE.md,
which is the file every agent on the repository reads before it writes anything. Both
agents then start from the same words, which is exactly the difference the build-off
measured. Install it with `claude plugin marketplace add AgentC-Consulting/aed-conventions`
and then `claude plugin install aed@aed-conventions`. Chapter 9 walks the whole
adoption, start to finish, in ten minutes.

Next: Chapter 3 · Naming that reads like a sentence (the convention you just picked,
one rule at a time).
