Vibe coding as a squad

A teammate's agent renamed what mine had just named, and one billing file held two vocabularies and a diff nobody could review. Nothing here rewrites anybody's work: we pick one convention, and the file agrees with itself again. Scroll, or press a stage.

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, and both apply a late fee. They drifted because nothing told either agent which words this repository uses.

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 against a perform that reads like a plan. The magic numbers, properties with defaults against a bare 30 and 0.05 in the method bodies, which is the one conflict that takes two lines on each side. The mailer, CustomerMail against CustomerMailer. A fifth is not in this file at all: the Customer both agents read from.

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 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, its late_fee_rate, and its 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.

apply_late_fee.cr · two agents
my agent · went in first
1class Billing:::ApplyLateFee
2 property invoice : Invoice
3 property days_late : Int32 = 0
4 property grace_period_in_days : Int32 = 30
5 property late_fee_rate : Float64 = 0.05
6
7 def process
8 if days_late >= grace_period_in_days
9 fee = invoice.amount * late_fee_rate
10 invoice.charge(fee)
11 CustomerMail.late_fee(invoice).deliver
12 end
13 end
14end
1# Applies a late fee to an invoice that has gone past its grace period, then tells the customer.
2class Billing:::ApplyLateFee
3 # -- What this needs --
4
5 # The invoice being charged. It knows its customer, its amount, and its due date.
6 property invoice : Invoice
7
8 # How many days past the due date the invoice is today.
9 property how_many_days_late : Int32 = 0
10
11 # -- Company policy --
12
13 # Days past due before any fee applies.
14 property grace_period_in_days : Int32 = 30
15
16 # The fee, as a share of the amount owed.
17 property late_fee_rate : Float64 = 0.05
18
19 # -- How it runs --
20
21 def perform
22 stop_unless_the_invoice_is_overdue
23 charge_the_late_fee
24 tell_the_customer
25 end
26
27 # Nothing happens inside the grace period.
28 private def stop_unless_the_invoice_is_overdue
29 return if how_many_days_late < grace_period_in_days
30 end
31
32 # The fee is a share of the amount owed, charged to the same invoice.
33 private def charge_the_late_fee
34 invoice.charge(invoice.amount * late_fee_rate, kind: :late_fee)
35 end
36
37 # The customer gets the notice by email.
38 private def tell_the_customer
39 CustomerMail.late_fee(invoice).deliver
40 end
41end
a teammate's agent · landed second
1class Billing:::ApplyLateFee
2 property invoice : Invoice
3 property how_many_days_late : Int32 = 0
4
5 def perform
6 return if how_many_days_late < 30
7 charge_the_late_fee
8 tell_the_customer
9 end
10
11 private def charge_the_late_fee
12 invoice.charge(invoice.amount * 0.05, kind: :late_fee)
13 end
14
15 private def tell_the_customer
16 CustomerMailer.late_fee(invoice).deliver
17 end
18end
1# Applies a late fee to an invoice that has gone past its grace period, then tells the customer.
2class Billing:::ApplyLateFee
3 # -- What this needs --
4
5 # The invoice being charged. It knows its customer, its amount, and its due date.
6 property invoice : Invoice
7
8 # How many days past the due date the invoice is today.
9 property how_many_days_late : Int32 = 0
10
11 # -- Company policy --
12
13 # Days past due before any fee applies.
14 property grace_period_in_days : Int32 = 30
15
16 # The fee, as a share of the amount owed.
17 property late_fee_rate : Float64 = 0.05
18
19 # -- How it runs --
20
21 def perform
22 stop_unless_the_invoice_is_overdue
23 charge_the_late_fee
24 tell_the_customer
25 end
26
27 # Nothing happens inside the grace period.
28 private def stop_unless_the_invoice_is_overdue
29 return if how_many_days_late < grace_period_in_days
30 end
31
32 # The fee is a share of the amount owed, charged to the same invoice.
33 private def charge_the_late_fee
34 invoice.charge(invoice.amount * late_fee_rate, kind: :late_fee)
35 end
36
37 # The customer gets the notice by email.
38 private def tell_the_customer
39 CustomerMail.late_fee(invoice).deliver
40 end
41end
Youopened the file first, which is an accident of order, not a house style.
The other developerreviews a diff where half the lines are a rename neither of you asked for.
Your agentbuilt a theory of intent from the names it found, then wrote against it.
A teammate's agentread the same file once that draft had landed and reached a different theory.
The ownerhas to trust that the fee is charged when the policy says it is.

The marked lines in each pane are the four conflicts; the magic numbers take two lines on each side, which is why five lines carry a mark. The settled file is printed in full below, so nothing on this page depends on the stages.

Two cursors: one billing file, four hands

The file both panes show once the convention is picked.

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.

apply_late_fee.cr · the same file, either agent
src/billing/apply_late_fee.cr
1# Applies a late fee to an invoice that has gone past its grace period, then tells the customer.
2class Billing:::ApplyLateFee
3 # -- What this needs --
4
5 # The invoice being charged. It knows its customer, its amount, and its due date.
6 property invoice : Invoice
7
8 # How many days past the due date the invoice is today.
9 property how_many_days_late : Int32 = 0
10
11 # -- Company policy --
12
13 # Days past due before any fee applies.
14 property grace_period_in_days : Int32 = 30
15
16 # The fee, as a share of the amount owed.
17 property late_fee_rate : Float64 = 0.05
18
19 # -- How it runs --
20
21 def perform
22 stop_unless_the_invoice_is_overdue
23 charge_the_late_fee
24 tell_the_customer
25 end
26
27 # Nothing happens inside the grace period.
28 private def stop_unless_the_invoice_is_overdue
29 return if how_many_days_late < grace_period_in_days
30 end
31
32 # The fee is a share of the amount owed, charged to the same invoice.
33 private def charge_the_late_fee
34 invoice.charge(invoice.amount * late_fee_rate, kind: :late_fee)
35 end
36
37 # The customer gets the notice by email.
38 private def tell_the_customer
39 CustomerMail.late_fee(invoice).deliver
40 end
41end

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.

Two blocks of work, joined by one shared bar: two agents, one convention.

Who is in the file

Five readers are in this file, and only one of them wrote the line you are looking at.

  • 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

The rules are shared, and they live somewhere both agents read before they write.

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.

  • “Prefer the form that reads like a plain statement of intent. Reach for shorthand only when it makes the intent clearer, never just shorter.”The guiding rule, and the one a squad has to agree on before any of the others matter.
  • “Data models should be singular, and only concerned with their own individual behavior.”One row, one class, one set of manners.
    CustomersCustomer
  • “Classes should be name spaced according to the feature that is being implemented.”In this book: Billing::ApplyLateFee.
    NewCustomerSubscriptionBilling::ActivateNewCustomerSubscription
  • “Class names should be short statements or phrases that clearly express the process being performed.”The class name is the sentence the owner would say out loud.
  • “Class attributes of non-enumerable primitive types should be phrased as short statements for what the intended purpose of the attribute is.”This is the rule that settles the rename in the scene.
    namefirst_name
  • “Class attributes of enumerable (Array or Array-like) objects or types should be phrased with list_of_ or collection_of_ or array_of_.”The name says it holds many, before anyone opens the type.
    orderslist_of_previous_orders
  • “Class attributes for boolean types should be named as if the expression is a question beginning with if.”A yes-or-no name reads as the condition it guards.
    payment_method_presenthas_a_valid_payment_method
  • “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.”Which subscription? The name answers before anyone has to ask.
    subscriptioncurrently_active_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.
    processperform
  • 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.”
    calculate_feecharge_the_late_fee
  • 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.
Two text cursors on one ruled sheet: two agents editing the same file.

Why it matters to the agent

An agent edits against a theory of intent, and two vocabularies make two theories.

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.

Reading with an agent? Hand it the machine edition of this chapter. The source is the conventions' README.

Before and after

Conflict five, the one that is not in the billing file: one Customer, not two spellings of one Customer.

customer.cr · before and after
customer.cr — before
1class Customer
2 property name : String
3 property email : String
4 property subscriptions : Array(Subscription) = [] of Subscription
5end
customer.cr — after
1class Customer
2 property first_name : String
3 property last_name : String
4 property email_address : String
5 property list_of_all_active_subscriptions : Array(Subscription) = [] of Subscription
6 property is_this_an_enterprise_customer : Bool = false
7end

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.

zsh · aed-conventions
$ claude plugin marketplace add AgentC-Consulting/aed-conventions
$ claude plugin install aed@aed-conventions
$ claude
> /aed:adopt
Two commands in the terminal and one inside Claude, and both agents start from the same words.

Where the convention lives

A convention in one person's head is not shared, and one 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.

Chapter 9 walks the whole adoption, start to finish, in ten minutes.