aher.dev

Back to the blog

This is the engineering story. The main ThriftySpark post, written for founders and product people, is now ThriftySpark: AI that puts 25,000 thrift stores online. It covers the problem, the economics, the market, the competition and the business model. This page stays as the record of how the product was re-aimed and rebuilt in one day by a dozen AI agent sessions.

Case study, 2026

ThriftySpark: a live product, one day, and a dozen AI agents

ThriftySpark is a SaaS I founded for thrift stores, and paying stores sell on it. You photograph an item, AI writes the listing, and it goes up on a hosted storefront. In October I gave the product one day and about a dozen AI agent sessions, with me as the only human. The first thing I asked was where a thrift store's next dollar comes from.

Verdict. Desk research put a thrift store's next dollar in the back room. An AI listing cannot pay for itself on a $3 shirt that already costs about $0.30 of labour to price. So I pointed the day at the back room, where donated bags become priced, tagged, marked-down stock, and a dozen agent sessions built four features for it with tests. The migrations are now applied in production and all four screens have run on a live store. What is left is three more stores and a week of use.

1. Fix the old product, question it, then build what the answers chose

385backend tests, up from 24, per the fix reports
4features built with tests
12agent sessions, plus about 200 reviewers
63defects found by the kickoff audit

Built here means tested on mocks and merged. The day also produced 72 commits, about 47 backend fixes, 114 frontend fixes and 41 frontend tests, up from 5.

Research re-aimed the build between the two phases

Phase 1 fixed what existed and asked whether it was the right product. Phase 2 built what the research pointed to, once a scaffold step had fenced the files. Above the orchestrator, a watchdog kept an eye on the budget.

Watchdog (Haiku, every 10 min): pause, resume, switch modelwatches budgetOrchestratorFable, run by meroutes hand-offsbriefsTwo research sessionsThree fix sessionsStress-test agentsresearchRe-aimpitch wrong,13 jobs, pick 4fencesScaffoldstubs, nav,SKU serviceA1 MarkdownA2 IntakeA3 ImportA4 Register4 draft PRs, merges 5 to 8docs and fix PRs, merges 1 to 4Merge, in order1 docs, 2 backend fixes, 3 frontend fixes, 4 stress test, 5 A2, 6 A3, 7 A1, 8 A4

Left to right, two research sessions decided what the four green boxes would be. The fixes ran alongside.

2. A $3 shirt cannot pay for an AI listing

The original pitch was “AI listing from a photo, for every item”. I had one session read job postings, trade blogs and Goodwill's disclosures for what a thrift store earns and spends. One paragraph about a $3 shirt sent the product somewhere else.

Labour already eats a tenth of a $2.99 price

One donation-processor job posting asks for 35 pieces an hour at $10 to $12 an hour. I call that the sticker-gun benchmark. It comes to $0.29 to $0.34 of labour a piece before anyone takes a photo, calls a model or checks the result. One store reports about 80% of its floor at $2.99 and under.

Labour per piece
$0.29 to $0.34
Price ceiling for 80% of one store's floor
$2.99

So photographing every item adds cost. That labour line already has to cover sorting, pricing and tagging, and a photo, a model call and a human check would land on top of it for every $2.99 shirt. I have not costed that step; the pilot will time it. Selling the shirt online costs more again. eBay takes 13.6% plus $0.30 to $0.40 an order, about $0.71 to $0.81 on a $2.99 sale before shipping, and eBay's own listing tool is free.

The AI pays its way on the top slice, the pieces worth listing online, which I assumed is 5 to 15% of items. The pilot will measure it as the share of intake items flagged worth listing.

Online selling matters, but it is a tail. ShopGoodwill sold about $450M in 2025, up 22%, and Goodwill's own AI tool lists 130,000 to 140,000 items a month. That is still under a tenth of Goodwill's retail revenue. Goodwill uses AI for the online tail; the shop floor runs without it. The sources disagree on market size, so I quote none. This sprint tested where to enter. Sizing can wait.

That one session's reading moved the product into the back room (intake, markdowns, the till) and kept AI for the few pieces worth listing online.

3. Build for the manager of 1 to 5 stores

The use-case session built five composite personas from job postings, trade blogs and volunteer guides. Nobody has been interviewed yet. It is the weakest part of this section, and a pilot fixes it first. Each chip shows the segment a persona landed in.

  • Primary Maria, manager of an independent thrift store with a few paid staff. Too small for enterprise systems. A general POS plus spreadsheets takes more time than she has.
  • Primary Dale, volunteer coordinator at a charity shop. Maria's jobs, plus roles and a PIN login so a volunteer never sees revenue or voids a sale. Harder to serve until roles ship.
  • Secondary Priya, owner of a consignment boutique. She has the need, but she pays consignors on the price actually paid, and purpose-built tools already exist. Later, once there is a consignor ledger.
  • Out Sam, full-time reseller. Wants cross-listing and sold comps, which mature tools already do. Useful as a buyer on store storefronts.
  • Out Jo, store manager at a charity chain location. Pricing rules and processing clocks are set by head office, on the chain's own stack.

One primary segment, one secondary, two out

The report recommended independent thrift stores and small charity shops with a paid manager and 1 to 5 locations, and asked me to decide whether consignment joined the plan. I kept it out until a consignor ledger exists.

Full-time reseller?Chain location run from head office?Takes goods on consignment?nononoyesReseller: a buyer on storefrontsno fit as a customer: SamyesCharity chain: own stackno fit: JoyesConsignment boutiquesecondary: Priya, needs a ledger firstIndependent thrift, small charity shopprimary: Maria, Dale; 1 to 5 storesbuild forafter a ledgerThe wedgeintake, import,register, markdown

Build for Maria and Dale now. Priya waits for the ledger. Sam and Jo are out.

4. The empty corner: AI plus store operations on one stock count

I asked for a map of the tools a thrift store could buy instead. The competitor session profiled about 20 products in 7 groups. Listing text is already free. eBay gives it away and reports over 500M AI-assisted listings, so a product that only writes listings is up against a free one.

The gap is in operations. No reviewed tool does donation intake, AI pricing, tag printing, floor sales and online sales against one stock count.

The map puts the main products on two axes: where the product lives, and how much of the work it does for you. I placed them by judgement. Nothing here was measured.

Online resaleIn-store operationswhere the product livesAI-assistedManualhow much of the work it does for youPOS and consignmentAI plus store ops: only ThriftySpark above the lineThriftySparkeBay AI listing (free)VendooList PerfectlySimpleConsign, on the lineThriftCartSquareShopify POSLightspeedConsignCloud

On the in-store side, ThriftySpark is alone above the line. Everything else lives online or leaves the work to staff.

What the alternatives cost a month

$0$40$80$120$160entry price per month, vendors' own pagesSquare $0Vendoo $15List Perfectly $29Shopify $39Square Plus $49Lightspeed $89ThriftCart $99ConsignCloud $139SimpleConsign $159ThriftySparkno price yet

Why not ThriftCart, and what this would cost

ThriftCart is the closest direct competitor, a nonprofit thrift POS from $99 a month that claims over 500 stores. No tool the research found, ThriftCart included, runs the back room on the same stock count as the till. By that I mean intake at sticker-gun speed, pricing, tags, and the valuable tail listed online from the same record. That combination is the wedge, and the pilot tests each part of it.

I would not call the code a moat. If ThriftCart ships intake first, the importer makes ThriftySpark something a store runs alongside ThriftCart, and the only asset this plan creates is pilot timings and store relationships.

Reporting waits because reports need sale records, and nothing created full ones until a register with tax, tender and discount existed. Weak reporting is the most repeated complaint about the category leaders, so end-of-day reports are the first build after the pilot.

ThriftySpark has no public price. The purpose-built tools charge $99 to $159 a month. Any price I set has to sit below what a store saves in processing labour and recovered margin, and neither number exists until the pilot measures intake speed and markdown recovery on a shop floor.

5. The product was strongest where the money was smallest

The use-case session ranked 13 jobs by the money at stake for a store doing a few hundred sales a day. Its evidence is sourced. The ordering is its own judgement, the report says so, and I accepted it. The old product was strongest on the online tail and weakest on markdowns and the till. Intake, the first job of a thrift store's day, did not exist at all. The table has the report's top five.

#JobEvidenceAfter the sprint
1Price right the first time and catch valuablesPricing on quality is the trade's first rule; a mispriced collectible is lost marginSkipped. The product already had an AI price and a retail range, and sold comps need marketplace data it does not have.
2Move aging stock with automatic markdownsLadders are the norm: 25, 50 and 75% off over three weeks at one store, a weekly half-price colour at Goodwill, a 21 day floor cycle in Columbia, MissouriBuilt as A1. A configurable ladder with preview and confirm.
3Ring up sales quickly and accuratelyTill and register entry errors are a named source of shrink in retail shrink guidesBuilt as A4. A proper till with discount, tax, tender, receipt and void.
4Process donations to the floor fastThe sticker-gun benchmark: 35 pieces an hour at $10 to $12Built as A2. Keyboard-first intake with a pieces-per-hour readout.
5Stop leakage at the donation door and tillThrift does not track an item until it is priced, so theft between the door and the tag is invisibleNot started. Needs roles and an audit log, which need the register first.

Three of the top five became the build. The other two wait on data and on the register. The product's best feature, the storefront, served job 7. Intake ranks fourth on money but comes first in the day, so it is the pilot's first measure.

6. Four features chosen by money, dependencies and file ownership

The report proposed 13 features (14 numbered, one folded into the register). Some cannot start until others exist. The picture shows the hard dependencies. Green is what was chosen for the build. Dashed boxes are infrastructure.

A4 Register v2cart, tax, tender, receiptSKU serviceinfrastructure, built in the scaffoldA1 Markdown rulesEnd of day, cash countReturns and holdsOffline queueConsignor ledgerA2 Intake batchesA3 Catalog importTags and racksenables: each needs a sale recordenables: each needs server-issued SKUsenables: tag colour by age

I adopted the report's rule. Start with the highest-money jobs (its jobs 2, 3, 4 and 10) that need no other feature, live in disjoint files and can be tested against mocks. That gave four:

  • Markdown rules: job 2.
  • The register: job 3, and the base for four later features.
  • Intake: job 4, and the job the kill criterion measures.
  • Import: job 10, because it removes the cost of switching from Square or a spreadsheet.

Job 1, pricing right the first time, was skipped because sold comps need marketplace data the product does not have, and eBay gives listing text away. Leakage waits on roles and an audit log, which need the register. Four features is the smallest product a pilot store can judge in a week, and intake and the register are the two jobs the kill criterion measures.

What the report decided, and what I decided

What the report didWhat I did
Ranked the 13 jobs, proposed the 13 features, found the four shared files and wrote the rule for the first four.Framed the question (does a thrift store need this?), set the ground rules and the file fences every brief carried, and chose the merge order.
Ended with a decisions-needed list: pick the segment, find pilot stores before marketing, run a one-week intake speed test.Answered it: independent thrift plus small charity, consignment out for now, pilots before any marketing, pieces per hour against the sticker gun as the success metric.
Changed the pitch.Took the research over my own pitch and pointed the day at the back room.

Fence the files first and the merge order follows

Four writers in four worktrees will collide on any file they all touch. The report found four shared files every feature needed, among them the file that registers every router, the dashboard navigation and SKU creation. If each branch adds a line to the same file, every merge after the first conflicts, and those merges happen at the end of the day when the budget is lowest.

Before: four writers, three shared filesA1A2A3A4router registrydashboard navSKU creation4 edits to each file: a conflict on every merge after the firstAfter: one scaffold, then fencesScaffoldrouter stubs, all registerednav entries, all addedone SKU serviceA1own route, page, API client, testsA2own route, page, API client, testsA3own route, page, API client, testsA4own route, page, API client, testsA2 and A3 call the SKU service8 branches merged in order; the scaffold merge needed the most care

So a scaffold step ran before any feature started. It registered every new router up front as an empty stub, added every nav entry, gave each feature its own API client file and added one server-side SKU service for everyone to call. Each writer then owned a fenced set of files, and the merge order more or less wrote itself. Eight branches merged in order at the end of the day. The fiddly one was the scaffold, because the backend branch had to take it in.

7. The brief was my only lever on quality

Four roles ran on three models. The one rule was that no agent edits a file it does not own.

RoleModelWhat it did
OrchestratorFableWrote every brief, sequenced phases, routed hand-offs, merged branches. Run by me.
WatchdogHaikuWoke every 10 minutes, read plan usage, could pause, resume or switch models.
WorkersSonnetOne session per job: six in phase 1, four features in phase 2.
ReviewersSonnet (sub-agents)Short-lived adversarial reviewers inside worker sessions: 87 over two backend runs, 112 in the frontend audit.

Six rules every brief carried

The first two are the fences from the picture above. The rest are about reporting.

  • One git worktree per writer. Readers share the main checkout and write only to the docs folder.
  • Touch only the directories named in the brief. Anything outside becomes a hand-off.
  • Never touch the production database or the production backend.
  • End every report with what was verified and what was not, as separate sections.
  • Every document carries a diagram, and frontend work follows the product's design constitution, so a dozen authors read as one.
  • Commit on a branch and open a draft PR.

Phase 1 ran six workers under those rules, and every fix branch merged before any feature branch.

The budget was a hard constraint

The team ran on a plan with usage limits. At kickoff the five-hour window was 16% used and 17% of the weekly allowance was left until it reset six hours later. The watchdog had three rules:

  • Pause everything at 92% of the window if its reset was more than ten minutes away.
  • Treat the weekly limit as a hard stop.
  • At 97% of weekly Fable usage, tell me, and I would switch the orchestrator to Opus.

In the end the budget stopped the backend's final verification early and cut two of seven frontend reviews short. I did not log the final usage, so I cannot say what the day cost. Next time the watchdog writes its last reading into the report.

Fences turn shared edits into hand-offs

The frontend session found that customer login failed after a frontend change, and the fix lived in backend code it was not allowed to touch.

Frontend sessionOrchestratorBackend session1. login fails;needs a backend change2. brief: the backend side of the login fix3. fixed on the backend branch, tests pass4. rebase and verify the login flow

The admin access check and the script-escaping fix crossed the fence the same way. It is slower than one engineer editing both sides. In exchange, across the three hand-offs I know of, no session edited a file it did not own.

Two things I would do again as a PM

  • Write the brief as the spec. Briefs that named the files, the tests and the definition of done came back as mergeable PRs.
  • Split reviewers by risk. The stress test and the fix sessions found several of the same bugs from different angles. That raised my confidence, and it also paid for those bugs twice in tokens. Next time each reviewer gets one risk area.

I wrote no code that day. What I controlled was what each brief asked for and which files it fenced off.

8. Four features, built so a retry never charges, discounts or imports twice

A retry, a double click or a second till lands on the same markdown step, sale or import row as the first attempt. That holds for three of the four. For intake it holds only when a batch is closed. The old create-listing API still makes a duplicate on retry, so the register got an idempotency key. Each feature had one session, its own report and its own tests.

FeatureThe jobThe decisionWhat can never happen twice
A1 Markdown and aging rulesOne price ladder. Stale stock gets cheaper on a schedule and nobody has to pull coloured tags by hand. Default 7, 14, 21 and 30 days at 10, 20, 35 and 50% off.Preview first, apply only on confirm. Percent off always comes from the original price. 43 new backend tests.A double-discount. Each item records its step, and an apply only lands if the step is still what was read, so two managers clicking at once cannot discount an item twice.
A2 Intake and donation batchesTurn a donor's bags into priced, SKU-tagged draft stock at sticker-gun speed: a letter picks the category, digits type the price, Enter adds.Keyboard first, 1 to 200 items per request on one SKU call. The donor receipt lists counts by category and the query that builds it never reads the price column. 16 new tests; not yet timed on a shop floor.A second close. Closing a batch is idempotent and a closed batch refuses new adds. Adding items is not guarded the same way.
A3 Catalog importMake switching cheap. Drop a CSV from Square, Shopify or a spreadsheet and the stock comes in as drafts.Preview and commit run the same analysis, so the preview is exactly what lands. Caps of 5 MB and 5,000 rows. 33 new tests, on fixtures built from the documented column sets alone.A duplicate SKU. Duplicates inside the file and against the store's existing SKUs are skipped.
A4 Register v2A proper till: several items, line edits, discount, tax, cash with change or card recorded for an external terminal, one charge, a receipt, a same-day void.All money in integer cents, half-up rounding. Worked example: $15.00 subtotal, $3.00 discount, 10% tax, $13.20 total, $20 cash, $6.80 change. 47 backend and 37 frontend tests.A second charge or a double sale. Every sale carries an idempotency key, so a retry after a dropped connection returns the same sale, and a one-off piece is marked sold only if it is still active, so two tills cannot sell it.

The register's worked example is arithmetic on a mock; a pilot store is the first real till.

9. 63 defects found, six money-risk bugs fixed, nothing proven in a store

A read-only audit at kickoff catalogued 63 defects before anyone wrote a line. Nobody tracked closures against this list, so I cannot say how many of the 63 are closed. The six below are.

Critical
6
High
22
Medium
17
Low and cleanup
18
of 63

Six bugs that could have cost a store money

Each one is generalised here; the fix reports carry the detail.

SeverityWhat was wrong, and what changed
CriticalA set of admin functions had no access check at all. They now require a platform admin.
CriticalA store name could inject script into every visitor's page. Escaped on the frontend; the backend now refuses such names.
HighCard payments were charged a cent short on some prices because of floating-point maths. Now decimal rounding, zero totals refused, and confirmation is idempotent.
HighAnyone could place unpaid orders without limit, and each one held a store's one-off items indefinitely. Orders now need a name and email, are rate limited, and expire after 24 hours.
HighSigned-in customers' orders were saved as guest orders, so customers never saw them; cancelling an order never put its items back on the shelf.
HighTwo shoppers could reserve the same one-off item at the same moment, and holds never expired. Now guarded, expiring, and a sweep heals stranded holds.

Zero failures on mocks, 91 on a backend with no database

The stress session wrote six persona agents. Among them were a store owner going from signup to an eBay export, an abusive user forging logins and a shopper driving a real browser.

  • Mock backend: 8,141 steps across 197 persona instances, zero failures, one low finding.
  • Real backend with no database attached: 374 steps, 91 failed; most were the missing database, and the run surfaced two real code defects.
FindingSeverityEffectStatus
Retried create-listing made two identical listingsLowDuplicate stock on a timeout.Open and tracked; A4's approach is the fix.
Malformed numbers in a request bodyHighA clean validation error became a server error, one root cause across five endpoints.Fixed
A declared default rate limit was never appliedHighEvery route was unthrottled.Fixed
The limit. On the day, nothing ran against a live database. A passing mock run proves the harness works. It says nothing about whether production is safe. Since then the migrations have gone into production and the four screens have been used on a live store; the stress harness has not yet been rerun against that database.
The strongest objection. Why would a store trust a till built in a day by AI agents and used in production for only a few days? My answer is that it should not, yet. The register runs beside the store's existing till for a week, with end-of-day totals compared each night, until a full week matches.

10. Done means three pilot stores beating the sticker gun

For the product, done means:

  • Three pilot stores running intake and the register.
  • Intake that beats the sticker-gun benchmark on a real shop floor.
  • All four features used for a full week without someone falling back to paper.

What stands in the way

  • Done since the sprint. The four migrations are applied in production, and intake, import, the markdown preview and a cash sale with a void have all been run on a live store. Two schema gaps turned up on the way and were fixed. Still open: the markdown ladder has been previewed but never applied, and the stress harness has not been rerun against the live database.
  • Known risks. The backend's final verification pass was stopped early to save budget. A short list of backend hardening items remains open and is tracked, and the frontend still lacks skip links and focus traps.

Recruiting goes through the channels the research already used: charity-shop volunteer networks, the thrift trade newsletters in the sources, and independent stores I can walk into. The first conversation follows the report's 20-minute interview script. Stores get free use for the pilot and pay only in time. I will log how many conversations it takes to land three, since that ratio is worth knowing too.

Week 1rerun the stress harness onthe live database; apply markdownWeek 2one-step register sale,nightly markdown, store 1 inWeek 33 stores on intake andthe register; time intakeWeek 4read a week of real use;decide the next buildGate: harness passeson the live DB?no: stop and fixbefore any storeKill gate: intake beats thesticker gun on a real floor?no: drop intake,test the register aloneRegister paper-freefor a full week?no: the wedge is wrong,back to the jobs tableIf both gates pass: end-of-day reports and staff roles are first in line.

The kill criterion

  • If intake cannot beat the sticker gun on a real floor by the end of week 3, I drop intake from the wedge and test the register alone.
  • If no store runs the register for a full week without going back to paper, the wedge is wrong and I go back to the jobs table.

A dozen agents read the market and built the product in a day. Deciding what to accept from them was my job, and so is proving it in a store. None of that tells me whether a store will use it. Three stores and four weeks will. The first step, running all four screens on a live store, is done.

Sources