On September 11 I walked into HackCBA at Casa Naranja X in Córdoba with three friends and a sleeping bag. Twenty four hours later we had lost. The winning team had a great product. I still went home with a paying client for what we built, because I had sold it nine days before the opening ceremony.
The product was already sold
Multimpresos is a print shop in Córdoba capital. Three people answer WhatsApp all day from a price list of about forty pages. Every price update is done by hand, product by product.
Before me, the owner paid for an AI agent. It ran for four or five months. It invented store addresses and branches in the north that do not exist. It quoted a 1,500,000 peso job at 14,000. Customers walked into the shop holding a phone with the fake quote on the screen.
I had two calls with the owner on September 2. On the afternoon of the hackathon he confirmed the monthly cost over WhatsApp: "if it's cheaper, more convenient and practical, then we go this way." Five hours later the organizers opened the tracks. I asked my team for a better idea. Nobody had one. So we built the agent I had already sold, and called it Dante.
Dante has one rule. The engine computes the price. The model only writes it down. Every incoming message opens a turn, and the turn runs in three phases, of which the middle one never touches a model. Extraction reads the message into a typed intent. Resolution finds the catalog row and computes the amount with a pure, tested function. Writing hands the model a number that is already settled.
No exact match in the catalog, and a person takes the conversation. No guessing, no "roughly". That is where the invented branches die.
Four people, four lanes
- Talisman did security. A threat model in the first hour, a gitleaks hook, the fencing of untrusted text (wrapping the customer message in markers so the model reads it as data and never as an order), and a full audit before the demo.
- Pato connected ElevenLabs. The owner changes a price with a voice note. The audio never writes: it proposes, and a person confirms. Pato had three hours, Friday 21:00 to midnight.
- Juan Bautista did the catalog and the database. Seed rows typed off the price list, the pricing function, the module math, and the pricing cases.
- I did the integration in Mastra. The service, the shared types, the Telegram bot, the three phase turn, and the wiring between the lanes.
Nobody knew git
My three friends are vibe coders. Good ones. They ship fast with an agent. None of them had used git on a team.
The first two hours showed it. Commits went straight to main. Two of them were called "test: this should be blocked": a hook test with fake secrets, pushed to the branch everyone else pulled from. Branches pulled main back in and filled up with conflicts. Nobody read anybody's code.
So I stopped the work and taught three things. Tickets in Linear, each with an owner and a time slot. One branch per ticket, named by its id. A pull request with a review, and tests and typecheck green before the merge.
The first pull request merged at 23:14 on Friday. By eight on Saturday morning there were 41. My favorite commit of the night is Pato's: "Address the review on #2: separate our failures from the owner's." That is the moment the team stopped treating a review as paperwork.
Test first, and let the agent grill you
An agent writes code fast. It also writes a test that agrees with its code. So the rule was test first. Juan Bautista agreed the pricing cases before a line of the pricing function existed. One of his commits says it well: "No amount leaves the engine without VAT, and a test that can prove it."
The second habit was grill-with-docs. It is a skill that interviews you about a design, one hard question at a time, and writes the decision down as it goes: an ADR for what is hard to reverse, a glossary for the words. The human decides before the agent guesses.
We wrote 28 ADRs in less than a day. Their titles read like rules: "An escalated conversation is over". "The receipt store has no reader". "A percentage is not an amount". When two lanes disagreed at two in the morning, the answer was in a file, not in somebody's memory.
The best bug of the night was one no test could catch. Two branches each did the right thing. One fenced the customer message inside the turn. The other moved the fencing up to the webhook. On their own, both were green. Together, every customer message reached the writer fenced twice, because each branch ran its own half and neither knew about the other. A clash like that only shows up once the two branches meet, and git had nothing to say about it.
Talisman audited the whole thing before the demo. It found eight issues. We checked each one against the code before we touched anything. Three got fixed. Four we rejected in writing. One we deferred.
We lost
At demo time Dante quoted a real product from the real price list, sent a question it could not answer to a person, took a price change from a voice note, and read a deposit receipt. The repo is public.
The winners had a great product. HackCBA's FAQ is clear about where a project has to start: "You can come with the idea worked out, but not with the repo started." The winning team's public hackathon repo opens with a commit at 20:17 on Friday, thirteen minutes before the hacking started. That commit imports 338 files and 70,089 lines from an earlier repo. The README describes that code as "the demo that was presented to YC".
First place paid USD 500. The shop's deal is worth more than that, and it was signed before we wrote the first commit. Losing cost us the trophy, not the money.
The code did not stop on Saturday. In the hackathon the four of us merged 160 commits into main. In the two days after, I added 645 more.
What the demo hid
The demo ran. An audit of the same commit, two days later, says what it was standing on.
Seven stores lived in a Map inside the process. A price the owner had confirmed by voice note was gone after the next deploy. A photo sent by a customer marked a deposit as paid with nobody reviewing it. The turn was three phases wired by hand, not a workflow, and a single agent did the writing. Three of the shop's thirty eight product families were loaded, which is 7.9 per cent of the price list. A fifth of the source was comments.
None of that shows in a three minute demo. All of it decides whether a shop can answer a customer on Monday.
What it looks like now
What survived is the domain core: the pure pricing function, exact match or escalate, and the fence around untrusted text. Everything else moved. Three Mastra workflows replaced the hand wiring. Thirteen tables replaced the Maps, so anything the owner confirms is a row. The deposit suspends the run until a person presses a button, and a price edit only applies if the row has not changed underneath it. An output processor checks the amount the writer produced against the amount the engine computed, and blocks the reply if they differ. Four scorers ride live traffic. Forty two families are seeded now, and the ADR count went from 28 to 56.
The part that was not code
We slept on the floor of the venue. The food was good, it never stopped, and it was free. I left with three friends who now open pull requests, and a few new ones from other teams. I have never had more fun losing.
What I would tell a team of vibe coders before their first hackathon:
- Teach git before the first commit. Ten minutes on branches saves the night.
- Bring a real customer. Then the result does not depend on the judges.
Dante goes to the shop this week. The judges gave their verdict on Saturday. On Monday the one that has to answer a customer is Dante, and that is the only score I care about.
I'm Federico Sapuppo. I build AI agents that quote from real data instead of inventing it. The hackathon repo is public.