tropo

Rung 06 · The Agentic Builder Series

In Tropo: Your First Working Thing


You read why one excellent path beats five hollow ones — that breadth is nearly free, and that's exactly the trap. Here you build the path: you drop in a finished spec for a client-intake tool, hand it to your agent, then do the harder half — you try to break it with your own hands and watch it refuse the garbage out loud. You don't write a word of the spec yet. About twenty minutes.


Part 1 — The Spec File

Here's the spec. You didn't write it — authoring is rung four. For now you're borrowing a good one so you can watch one complete path run end to end: a stranger fills in a form, the bad entries get caught and refused with a reason, the good ones land in a record, and every attempt gets logged. That whole path — not "the form" — is the one thing rung six asks you to make work first.

Which folder does this go in? The one your agent is already working in. If you're not sure which that is, just ask it: "What folder are you working in right now?" — and use the folder it names. (Many agents show it at the top of the window, too.) Everything in this walkthrough lives in that one folder.

Getting the spec into a file — the easy way. You don't need to hand-craft a .md file (and on a Mac, TextEdit will fight you — it likes to save .rtf and hide extensions). Let the agent do it. Paste the whole spec below into your agent and say:

Save the text below as a file named intake.spec.md in the folder
you're working in. Just save it exactly as-is — don't build
anything yet.

<paste the entire spec here>

That's it — the spec is now a real file in your folder, created by the agent, spelled correctly, no extension surprises. (If you'd rather make the file yourself and you're comfortable doing it, go ahead — the result is the same. Today it's just a file the agent reads; governing it comes later.)

Here's the spec to paste:

# intake.spec.md

**Version:** 1.0.0
**Owner:** you
**Status:** active

## Intent

A client-intake tool. Its whole job is to receive other people's
submissions, refuse the bad ones out loud, and file the good ones into a
record I can trust. This is the tracker growing up: the same kind of list
I might keep by hand, now machine-fed by people who are not me.

One path, done end-to-end and excellent. Not broad. A stranger fills in a
form; if the entry is bad it is caught and refused with a reason; if it is
good it lands in the record and the log notes what happened.

## What "done" means — the five-point excellent contract

This tool is done only when ALL FIVE are true. Each is a checkable line,
not a vibe.

1. **Survives bad input.** A blank required field, or a pasted paragraph
 in the phone field, is caught and refused with a plain-language reason.
 It is never silently accepted. The record stays clean.
2. **Tells you when it breaks.** Every submission attempt — accepted OR
 rejected — appends one line to an on-page `submissions.log` block:
 timestamp, outcome, and the reason if rejected. Nothing happens off
 the record.
3. **Versioned.** The working spec is version 1.0.0. Every meaningful change
 bumps the version and drops a copy in `versions/`. I can always see the
 last good version.
4. **Rules in a readable file.** The validation rules live in `rules.md`
 in plain English — what each field requires and why — so a non-coder
 can read them, argue with them, and ask for a change without reading
 the HTML.
5. **Run on real data.** Before this rung is done I type at least one real
 good submission and one real bad submission myself and confirm the tool
 does the right thing with each.

## The fields (the one path)

A submission has exactly four fields:

- **Name** — required. Non-empty after trimming spaces.
- **Email** — required. Must contain one `@` with text on both sides and a
 dot in the domain. Not a full RFC validator; just enough to refuse
 obvious garbage.
- **Phone** — required. Digits, spaces, `+`, `-`, `(`, `)` only, and
 between 7 and 20 characters. A pasted paragraph must be refused here.
- **Reason for reaching out** — required. Between 10 and 500 characters
 after trimming. A one-word answer is refused; a wall of text is refused.

A good submission passes all four. A bad submission fails at least one and
is refused with the specific reason(s), and the record is not touched.

## One worked example

**Good submission (accepted, lands in the record):**

 Name: Dana Reyes
 Email: dana@brightpath.co
 Phone: +1 (415) 555-0148
 Reason: Following up on the proposal you sent last week — we'd like to
 move ahead and need to talk timeline.

Result: the record gains a row for Dana, and the on-page log gains:

 2026-07-04T19:40:12 | ACCEPTED | Dana Reyes <dana@brightpath.co>

**Bad submission (refused, record untouched):**

 Name: (blank)
 Email: dana@brightpath.co
 Phone: call me whenever honestly i'm around most afternoons and evenings
 Reason: hi

Result: refused. The form shows three reasons: Name is required; Phone
looks like text, not a phone number; Reason is too short. Nothing is added
to the record. The on-page log gains a REJECTED line naming the reasons.

## Non-goals (explicitly out of scope this rung)

- **No second feature.** No export, no edit, no delete, no search, no
 email-sending, no dashboard. One path only: submit → validate → file or
 refuse → log.
- **No server, no database.** The record and log live in the page for now
 (I read them on screen, or copy them out). Persistence is a later rung's
 job — a file opened from disk cannot write files back.
- **No perfect validation.** "Good enough to refuse obvious garbage," not
 "bulletproof against a determined attacker." Catching the careless user
 is the bar for rung 6.
- **No styling beyond legible.** Light theme, readable, works when opened
 from a file. Polish is not the point; the working path is.

## Done-check (run before declaring the rung finished)

- [ ] Blank name → refused, reason shown, record untouched, log line written
- [ ] Paragraph in phone → refused, reason shown, record untouched
- [ ] Valid submission → accepted, row in record, log line written
- [ ] `rules.md` exists and matches the tool's actual behavior
- [ ] `versions/` holds the last good copy
- [ ] I ran a real good AND a real bad submission with my own hands

That's the whole spec. Notice what it does not ask for: no export, no search, no dashboard. It fences off breadth on purpose and spends every word on one path done well. The five-point contract at the top is the fence — you already write briefs that say "here's done, here's what to leave out." This is that instinct, aimed at an agent, with the leave out said as loudly as the build.


Part 2 — The Build Walkthrough

Three turns. Each one is a prompt you paste, and a look at what comes back. The prompts below are the ones that produced the intake tool in this walkthrough — they work as written.

A note on what you'll see: the conversation is text — copy the prompts straight out. The tool itself is the one thing you'll see as an image, because its whole point is that it's visual and it's yours. And one honest caveat about the replies below: your agent's exact wording will differ every time — these blocks are roughly what you'll see, not a transcript to match. What matters is that a file named something like intake.html appears, opens, and refuses bad input. If your agent asks you a question instead of building, just answer it or paste the prompt again. Tropo runs the same way across tools; the words matter, the chrome doesn't.

Step 1 — Hand the spec to your agent

The spec is now saved as intake.spec.md in your folder. Now direct the work. You're not writing the tool; you're handing off a clear ask to something capable and letting it run.

Paste this:

I dropped a spec in this folder called intake.spec.md. Read it and
build exactly what it describes — a single openable HTML file I can
double-click, that validates the four fields, refuses bad input with
reasons, files good submissions into an on-page record, and logs
every attempt to an on-page submissions.log block. Also write the
plain-English rules.md the spec asks for, and drop a copy of the spec
in versions/ as intake.spec.v1.0.0.md. Don't ask me questions, just build
it to the spec.

Roughly what comes back:

Read the spec. Built the one path end to end. Four files now in your
folder:

 · intake.html — the tool: form, in-page record, in-page
 submissions.log block. Open it and it runs;
 no server.
 · rules.md — the validation rules in plain English, a
 mirror of what the HTML actually enforces.
 · versions/intake.spec.v1.0.0.md — the version-1.0.0 copy of the spec.
 · (the record and the log are blocks ON the page — a file opened
 from disk can't write files, so they live in intake.html and
 you read them there.)

The four fields validate per the spec: Name non-empty; Email one @
with a dotted domain; Phone 7–20 chars of digits/spaces/+-() only;
Reason 10–500 chars. Bad input is refused with a plain-language
reason and the record is left untouched. Every attempt — accepted or
rejected — appends a line to the on-page log.

Open it with: open intake.html

Now open it. Find intake.html in your folder and double-click it — it opens in your browser. That's the whole move. (If you're comfortable in a terminal, the agent's open intake.html does the same thing from the command line — but you never need the terminal for this.)

[screenshot: your working intake tool] A light bone-and-white page, one column. A heading "Client Intake" and a subline: "Version 1.0.0 · one path: submit → validate → file or refuse → log. Rules live in rules.md." Below it a form with four fields — Name, Email, Phone, Reason for reaching out (10–500 characters) — and a blue Submit button. Under the form, two panels: "The record" (empty, reading "No submissions yet.") and a dark "submissions.log" block reading "(empty)."

You made that happen. You wrote nothing but a hand-off — and what came back isn't a demo screen, it's a path with a fence around it.

Step 2 — Check the rules match the tool

Before you trust the tool, check the one thing rung six leans on: the rules the agent says it enforces are the rules it actually enforces. The spec demanded a plain-English rules.md precisely so you can read the behavior without reading the HTML. So read it — and make the agent confirm the two agree.

Paste this:

Show me rules.md, and confirm it matches the validation the tool
actually runs in intake.html — same field requirements, same phone
length, same reason length. If they disagree anywhere, tell me where.

Roughly what comes back:

rules.md and intake.html agree. Field by field:

 · Name — required, non-empty after trimming.
 · Email — one @, text before it, a dot in the domain, no spaces.
 · Phone — digits/spaces/+-() only, length 7–20.
 · Reason — 10–500 characters after trimming.

On accept: cleaned values become a new record row and the log gains
"<timestamp> | ACCEPTED | <name> <<email>>". On refuse: nothing is
added to the record and the log gains "<timestamp> | REJECTED |
<reasons>". rules.md states each of these in plain English. No
disagreement found.

Don't stop at the agent's word for it. The next step and Part 3 are where you push on the tool yourself — the half that proves it's correct, not just finished-looking.

Step 3 — Make the agent attack its own build

This is the move the essay's Power Play named, and it's the heart of rung six: before you widen anything, make the tool prove it survives bad input. You don't invent the attacks — you make the agent do it, because it knows where its own build is thin.

Paste this:

You built this. Now try to break it. Give me five inputs a real
user — careless, rushed, or hostile — might enter that would make
this fail or quietly do the wrong thing. Then run each one against
the actual validation and show me exactly what happens to the record
and the log for each.

Roughly what comes back:

Ran five against the real validate() logic:

1. Blank name, valid rest → REFUSED. "Name is required." Record
 untouched. Log: REJECTED line.
2. Pasted sentence in phone ("call me whenever honestly i'm around
 most afternoons and evenings") → REFUSED. "Phone must be 7–20
 chars of digits/spaces/+-() — that looks like text, not a phone
 number." Record untouched. Log: REJECTED line.
3. Reason = "hi" → REFUSED. "Reason is too short (min 10
 characters)." Record untouched.
4. Email = "dana@@brightpath" (two @, no dotted domain) → REFUSED.
 "Email must look like name@domain.tld."
5. All-blank submit → REFUSED with all four reasons at once. Record
 untouched. Log: one REJECTED line listing every reason.

One honest caveat: this refuses careless garbage, not a determined
attacker — that was a spec non-goal. Within the bar rung 6 set, the
record stayed clean on every bad input, and every attempt hit the log.

That's the tray-of-muffins test run while it's still one muffin. Nothing here was built on top of a hollow path, because you checked the path was solid first. You didn't need to know how the validation is written — you needed to make the agent point it at the edges and show you the record stayed clean.

(A lighter version of this move, if you want to feel it with less at stake: change one rule in the spec — say the phone minimum from 7 to 10 characters — bump the version to 1.1.0, drop a versions/intake.spec.v1.1.0.md copy, and ask for a rebuild. One number in the spec, one changed refusal in the tool, rules.md updated to match. That's the same discipline — rules in a readable file, versioned — on a smaller swing.)


Part 3 — The Verification Checklist

Directing is half the job. This is the harder half — deciding whether what came back is right, not just whether it exists. For an intake tool, "it looks finished" is especially treacherous: a form that accepts everything looks identical to one that guards the gate, until a real stranger pastes a paragraph into it. So don't read this as a row of checkmarks to admire. Perform it — with your own hands, on the live page. You review other people's work all the time; you know the difference between glancing and actually checking.

Go to the open intake.html and do this now, before you call it done:

  1. Submit a good one. Type a real name, a real email, a real phone like +1 (415) 555-0148, and a real sentence of ten-plus characters. Submit. You should see a green "Accepted" message, a new numbered row in The record, and one new ACCEPTED line in the submissions.log block. If the row doesn't appear, "files the good ones" is décor, not function.
  2. Paste a paragraph into Phone. Type a real name and email, then paste something like "call me whenever honestly i'm around most afternoons and evenings" into Phone, and a short reason. Submit. It must be refused with a plain-language phone reason, and The record must not gain a row. This is the whole point of the rung — if garbage lands in the record, the tool failed the one job it had.
  3. Submit an all-blank form. Click Submit with every field empty. You should get all four reasons at once, and again the record stays untouched. One refusal listing every problem beats four round-trips.
  4. Read the log block after all three. The on-page submissions.log must now show a line per attempt — your ACCEPTED line and your two REJECTED lines, each with a timestamp. This is the tool telling you what it did; if an attempt is missing from the log, "nothing happens off the record" is broken.
  5. Open rules.md and confirm it still describes what you just saw. The phone length, the reason length, the refusal reasons — the words in rules.md must match the behavior you just triggered. Rules you can't trust are rules you don't actually own.

Now fill in the receipt — with what you saw, not what you hoped:

What was askedOne complete client-intake path, done excellently before any breadth: a four-field form that refuses bad input with plain-language reasons, files good submissions into an on-page record, logs every attempt to an on-page submissions.log block, mirrors its rules in a readable rules.md, and keeps a versioned copy of the spec.
What you can now do yourselfOpen the tool, submit a real good entry and watch it file, submit real garbage and watch it be refused with a reason while the record stays clean, read the log to see every attempt, and read rules.md to know exactly what the tool guards against — without reading a line of code.
What the record showsThree real files sit in your folder: intake.spec.md, intake.html, rules.md, plus a versions/intake.spec.v1.0.0.md copy. The record and the submissions.log are blocks on the page — you watched them fill as you submitted. The evidence of correctness is that your garbage inputs were refused and the record never gained a bad row.
What's not done yetIt doesn't persist — close the page and the record and log reset, because a file opened from disk can't write files (that was a non-goal; persistence is a later rung). It's one path only: no export, no edit, no search. And today these are just files in your folder — not yet governed Tropo files that your studio tracks across sessions; that upgrade comes later. Today you made one thing work end to end and proved it holds. That's the whole win.

If a good submission didn't file, or garbage slipped into the record, or an attempt was missing from the log, or rules.md didn't match what you saw — you caught it, which is exactly the job. Send it back:

When I paste a paragraph into Phone, the submission is still landing
in the record — it should be refused with a plain-language reason and
the record left untouched. Fix the validation so bad phone input never
reaches the record, and confirm rules.md still matches.

Directing includes rejecting. That's not the loop failing; that's the loop working — and on an intake tool, catching the bad one is the product.


Part 4 — What You Now Have in Your Studio

You started without writing a word of the spec. You end holding one complete path — a stranger can hit this form, and the good gets in, the bad gets refused out loud, and every attempt is on the record.

In your folder, right now:

  • A working intake toolintake.html, a real single-file tool you can open and hand to someone. It guards the gate: bad input is caught with a reason, good input is filed, and you proved it by hand. Not a demo. The record and the submissions.log are blocks you watch fill on the page as submissions come in.
  • Rules you can read and argue withrules.md, the plain-English mirror of what the tool enforces. You confirmed the two agree, so you own the rules, not just rent them.
  • A versioned specintake.spec.md, with versions/intake.spec.v1.0.0.md holding the last good copy. Change a rule, bump the version, ask for a rebuild, and you can always see what changed and roll back.
  • Your receipt — the checklist above, filled with what you actually saw when you attacked the tool yourself. That habit — direct, then verify by trying to break it — is the one this whole rung exists to build.

It's doable. It's not automatic — you had to fence the spec down to one path, you had to make the agent attack its own build, and you had to paste the garbage in yourself to be sure it held. But you did all three, and it worked. Next rung: you stop letting the agent run to done in one shot and start checkpointing a longer build as it goes. For now — you made one thing work first, all the way through, and it holds.