DEV Community

Aghassi Sargsyan
Aghassi Sargsyan

Posted on

I launched to zero signups, then found 5 features nobody could reach

I spent months building an AI agent platform. I launched it on Product Hunt yesterday. Zero signups.

The comments were friendly. Three of the four asked for the same thing — not features, not integrations, not a lower price. They wanted to see what the agents did and what they cost. One put it better than my own landing page ever did: they liked that it wasn't "a black box."

So I went to make the cost dashboard better. Instead I found out my product had been lying to me for months, and the lies had a pattern.

Here's everything, with the code.


1. Every run cost $0.00

The Cost Analytics page reported $0.02 in total across ~100 executions. I'd assumed that meant the platform was cheap to run. It meant the data was being destroyed at write time.

cost_cents = int(
    (llm_response.prompt_tokens * 0.5 / 1000)
    + (llm_response.completion_tokens * 1.5 / 1000)
)
Enter fullscreen mode Exit fullscreen mode

A typical run on my platform is 56 prompt tokens and 45 completion tokens. That's 0.0843 cents. int() makes it 0.

Not some runs. Essentially every run — because almost every LLM call costs less than one cent. The production numbers: 189 agent runs, 2 with a non-zero cost. 99% of my cost data was zeroes, and the two survivors were just big enough to clear a whole cent.

The rates in that formula were correct. I checked them against the providers' pricing pages; the arithmetic is right. The bug is entirely int() on a value that is almost never ≥ 1. A Decimal would have been the textbook fix, but Decimal / float raises TypeError and ~80 call sites do arithmetic on this number, so I widened the column to a float and kept the unit (cents). It's a dashboard estimate, not money — Paddle handles money — so float rounding is irrelevant here.

2. Workflow costs were never recorded at all

Truncation at least loses precision. This one lost everything.

WorkflowExecution.total_cost_cents and total_tokens_used had no write site anywhere in the codebase. Not a broken write — no write. The columns had been NULL since the feature shipped. The workflow runner computed per-step token counts and dropped them on the floor.

I found this by grepping for assignments and getting no hits:

$ grep -rn "\.total_cost_cents = " src/
$ # (nothing)
Enter fullscreen mode Exit fullscreen mode

The API served the field. The schema declared it. The dashboard summed it. It was never once set.

3. The rates were per-provider, not per-model

if llm_response.provider == "openai":   # gpt-4o-mini billed at gpt-4o rates
Enter fullscreen mode Exit fullscreen mode

Same provider, ~30x cheaper model, same price. Nobody noticed because of #1 — every number was already zero.

4. The cost was rendered in exactly zero components

I fixed the data. Then I went to display it and found costCents had been in the API response for months and was rendered nowhere in the frontend. Not on the run page, not anywhere:

$ grep -rn "costCents" src/ --include=*.tsx
$ # (nothing)
Enter fullscreen mode Exit fullscreen mode

The TypeScript types never declared the field either — so the API was returning data that was invisible to the compiler. No error. Just absent.

And the workflow step list rendered this:

{step.tokensUsed && <span>{step.tokensUsed} tokens</span>}
Enter fullscreen mode Exit fullscreen mode

WorkflowStepExecution has no tokensUsed column. The response schema has no such field. That line had never rendered once. It was in the UI, in code review, in the repo, doing nothing.

5. Analytics had no link. Anywhere.

This is the one that stings.

The app has four analytics pages — Dashboard, Executions, Costs, System. They work. They have a nice sub-nav between them.

Nothing in the entire application linked to any of them. Not the main nav (Dashboard, Agents, Workflows, Executions, Marketplace, Teams, Schedules, Billing). Not the user menu. The only way in was typing the URL. And once you got there, there was no way back except the browser button.

The $0.02 that started this whole investigation was on a page no user could click to.

When I fixed it, I added the back-link to the shared AnalyticsLayout component. Then a browser test told me the link wasn't there. AnalyticsLayout is dead code — all four pages only import a sub-component from that file; the layout itself is never rendered. My fix for the unreachable feature went into unreachable code. I only caught it because I drove a real browser instead of trusting the diff.


The bonus round: a public endpoint

While making Analytics reachable, I exposed a System Health tab to every logged-in user. Looking at a screenshot of my own fix, I stopped: should a normal user see system health?

It was worse than the question. /v1/health/detailed had no auth dependency at all, and the reverse proxy sends /v1/* straight to the backend. Anyone on the internet, no account, could poll:

uptime · Postgres latency · Redis latency · Celery worker count
WebSocket → connections: 1, users: 1     ← live user count
Enter fullscreen mode Exit fullscreen mode

A real-time traffic gauge for my business, free to anyone who cared to watch. Public since the day it was written.

Why it survived every test: the test suite's conftest overrides get_current_user. Any test using the shared client fixture authenticates as a fake user — so a missing auth dependency looks identical to a working one. The endpoint would have passed an auth test. The regression test I wrote deliberately bypasses that fixture:

async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
    resp = await ac.get("/v1/health/detailed")   # no fixture, no override
assert resp.status_code in (401, 403)
Enter fullscreen mode Exit fullscreen mode

The pattern

Five separate bugs, one shape: built, tested, shipped, unreachable.

  • cost data → written, destroyed on write
  • workflow cost → schema'd, never written
  • costCents → returned, never rendered
  • per-step tokens → rendered, never sent
  • Analytics → built, never linked
  • current_step_id → tracked in the DB, unused by the frontend (still true; it's next)

Every one passed CI. 519 backend tests, 276 frontend tests, typecheck clean. Every one had a plausible-looking implementation. None of them worked end to end, and no test could have caught any of them, because each piece did exactly what its unit test said it should. The column stored an int. The endpoint returned JSON. The component rendered a field. The wiring between them was where the product lived, and nothing tested the wiring.

That's not bad luck. Five in one week is a process failure. Mine was that I verified changes and never verified features — I'd check that the code did what I wrote, not that a user could get to it and see a true number at the end.

The thing that actually found these was mundane: driving the real app, as a real user, and looking at the screen. Every single one was invisible from inside the codebase and obvious from the browser.

What I'd tell past me

A test that mocks your auth cannot test your auth. The fixture that makes tests convenient is the fixture that hides the hole.

"The API returns it" is not a feature. Nobody can see your JSON.

Grep for the write, not the read. A field being read in 80 places says nothing about whether anything ever writes it.

If a number looks suspiciously good, it's probably a bug. $0.02 across 100 runs should have made me suspicious months earlier. I filed it under "efficient" instead of "broken."

Click the thing. Not the diff. Not the test. The thing.


And the launch?

Still zero signups. The observability is honest now, the costs are real, and the pages are reachable — and none of that changed the number, because a product being correct was never what was stopping people. That's a different post, and I haven't earned it yet.

But I'd rather have found all this from four polite comments than from the first customer who actually paid.

The product is AgentMesh — no-code AI agents for small businesses. Zero strangers have ever signed up, so please don't take this as a recommendation. Take it as a warning about your own repo.

Top comments (8)

Collapse
 
dl_notes profile image
DL

The line that changes how I read this is near the end: fixing the observability did not change the zero-signup number.

That makes the bugs real, but maybe not the main demand signal.

The four Product Hunt comments asking what the agents do and what they cost sound like a different layer: people were not yet comparing features. They were still trying to understand the promise, the boundary, and the risk of letting an agent touch their work.

So I would separate two reads:

  • unreachable analytics was a product verification failure;
  • zero signups after the fix may still be a promise / trust / buyer-moment failure.

The useful follow-up signal might be whether any of those same commenters came back after the cost visibility was fixed. If they did not, the missing piece probably was not the dashboard.

Collapse
 
aghassis profile image
Aghassi Sargsyan

You're right, and I have the data for the exact test you proposed: none of the four came back. Not one of them ever created an account — before or after the fix.

So the dashboard wasn't the blocker. I built what they asked for and it moved nothing, which means I heard "we want cost visibility" when the actual signal was closer to what you describe: they were still working out what the thing is and whether they'd let it near their work.

The uncomfortable version: I optimized the part I could measure and control, because the other part — the promise, the trust, the buyer moment — is harder and I don't have a test for it yet.

Thanks for this. It's a better diagnosis than the one in my own post.

Collapse
 
dl_notes profile image
DL

That is a much cleaner test than the original bug list.

If none of the four came back after the fix, the fix may still be necessary, but it stops being the explanation for zero signups.

The public run page sounds like the right next move. I would just treat it as a trust test, not demand proof. It answers: can a stranger understand what this thing actually does without creating an account?

The next signal is narrower: after seeing one real run, do people bring you a specific workflow they want to try, or do they only say it looks transparent and useful?

If the reaction stays in the second bucket, the promise may still be too detached from a buying moment.

Thread Thread
 
aghassis profile image
Aghassi Sargsyan

The trust-test / demand-proof split is the part I'd have gotten wrong. I was going to build the page and count "this looks transparent" as a win — which by your framing is exactly the false positive.

So I'll write the success criterion down before building it: bucket one is someone asking "can it do X for my client", bucket two is generic praise. If it all lands in bucket two, the page worked and the offer still doesn't.

Noting it here so I can't quietly move the goalposts later.

Out of curiosity — is this how you think about products professionally? Your framing is sharper than mine and I'd like to know where it comes from.

Thread Thread
 
dl_notes profile image
DL

That is a much cleaner test than the original bug list.

If none of the four came back after the fix, the fix may still be necessary, but it stops being the explanation for zero signups.

The public run page sounds like the right next move. I would treat it as a trust test, not demand proof. It answers whether a stranger can understand what this thing actually does without creating an account.

The next signal is narrower: after seeing one real run, do people bring you a specific workflow they want to try, or do they only say it looks transparent and useful?

If the reaction stays in the second bucket, the promise may still be too detached from a buying moment.

Collapse
 
eric-evidence-gate profile image
Eric Choi | Evidence Gate Studio

The “black box” comment sounds like the strongest buyer signal here. Before adding more features, I’d make one complete run visible: input, agent action, cost, output, failure state, and what the user can verify. If the buyer cannot see what happened and why it is safe, the product can be working while the offer still feels risky.

Collapse
 
aghassis profile image
Aghassi Sargsyan

This lands. I checked after reading it: the run view already shows input, agent actions, per-step output, tokens, real per-run cost, failure state and one-click replay — and a prospective user can see exactly none of it, because it all sits behind signup.

So I built the evidence and then hid it from the only people who needed it to decide. Zero mentions of any of it on the landing page.

Fixing that is a public page with one real, complete run on it — real numbers, including a failed one. That's a much cheaper experiment than anything else on my list, and it tests your hypothesis directly.

"The product can be working while the offer still feels risky" is the clearest statement of my problem I've read. Thank you.

Collapse
 
conversionrescue profile image
Saul

You fixed the product truth, but the current page still asks one visitor to buy an entire “team of AI employees” for every SMB operation. The first concrete pain — a human copying data between orders, questions, and schedules — appears after a broad integration wall.

I would test one installable workflow as the product above the fold: “When an order arrives, check inventory, draft the customer reply, and alert Slack only when a human is needed.” Show its real run, cost, and setup time, then make the CTA “Install this workflow free.”

The platform breadth can justify the decision later. The first screen needs one buyer, one trigger, and one observable finished job. Transparency answers “can I trust this?”; the missing piece is still “which painful job should I hire it for first?”