the build story

75 days, from empty repo to production.

Three repos, 888 commits, and a task board kept since day one. Every date on this page is a commit date — the product's own history, not a retelling.

75

days building

888

commits

59

phases shipped

1,384

CI tests green

5

npm packages

0

open ports in production

51
320
543
1,033
1,384

the CI test suite, over time

July

Jul 5 first commit

Jul 5 → Jul 6

The engine

The whole multi-tenant delivery platform lands in two days: accounts through templates, API through dashboard.

  • Multi-tenant foundation — accounts, organizations, environments, API keys
  • Dashboard shell: auth, activity, workflow editor, message timeline, analytics
  • Node SDK, React inbox widget, subscriber tokens
  • Versioned MJML template system with a live-preview editor
  • Delivery guarantees in the spine from the start: retries with backoff, provider failover chains and circuit breakers, a dead-letter queue, idempotency keys, digest windows, priority lanes, suppression
  • Rebranded to Asyncify (@asyncify-hq) on Jul 6

Jul 6 → Jul 7

Making it shippable

The code becomes a product other people could install and read.

  • Dual ESM+CJS builds, package READMEs, LICENSE, registry metadata
  • First test suite: 51 Vitest tests, plus an instant API-key revocation fix
  • Engineering and email-deliverability skill libraries written down
  • README rewritten as a plain-English architecture walkthrough, with a request-flow doc — "anatomy of a notification"

Jul 7 → Jul 10

The agents track opens

Notifications stop being one-way. A reply comes back up the same pipe.

  • Conversation core: a two-way agent bridge riding the queue spine
  • @asyncify-hq/agent SDK, a self-registering demo brain, AgentChat widget
  • Telegram channel (webhook ingestion + channel-aware replies), then email (inbound webhook + replies through the provider chain)
  • Managed LLM brains — zero-code agents — then LLM tool use, usage accounting and per-agent token caps
  • Auto-resolve on inactivity; subscriber linking — one human, one subscriber, identity unified across every channel

from the board · ~Jul 9 → Jul 10

The phantom email, exorcised

A managed agent promised a confirmation email to a Telegram user who had no email on file — a model's lie that exposed something structural: to the platform, one human was three strangers (a Telegram chat, an email address, a widget session). Subscriber linking shipped — one human, one subscriber, identity unified across every channel — and the acceptance test was the failed scenario replayed to a pass: a real email landing in a real inbox, started from a Telegram chat.

Jul 10 token-less publishing live — Changesets + OIDC trusted publishing (PR #1)

Jul 10

Release automation and CI

Publishing stops being a person with a token.

  • CI on every push: typecheck, tests, builds (Node 24 runner)
  • npm workspaces across packages/*
  • Changesets + OIDC trusted publishing; first token-less release verified live the same day (PR #1) — the package was on npm 33 seconds after the merge, with a provenance attestation

Jul 11 → Jul 13

Depth, security and the CLI

A comparison sweep against a leading notification platform sets the agenda; six phases answer it.

  • SSRF guard on every tenant-supplied outbound URL (Phase 9)
  • Connection/endpoint model split — one credential, many destinations
  • Slack as a third agent channel
  • Cards v2: select and text-input cards, plan-card streaming
  • @asyncify-hq/cliasyncify dev and create-agent; the public URL becomes a runtime value instead of a build-time one

Jul 14 → Jul 19

Onboarding, tools and approvals

Agents get hands, and humans get a veto.

  • Onboarding: Slack quick-setup, rotation auto-update, welcome messages, QR flows; agents that speak first on a new DM
  • Agent tools: customer registry, signed execution, human approval, evals
  • Channel approvals — tap Approve/Deny straight from Slack or Telegram
  • Per-channel agent routing: one bot token serving three brains — #billing to one agent, #support and DMs to another — with zero webhook re-registration
  • A hand-rolled, zero-dependency QR generator (vendored, self-decoded in tests, proven by a real phone camera) keeps the React package clean

Jul 19 five packages live under @asyncify-hq — node, react, agent, cli, react-native

from the board · ~July → August

The DNS race no manual drill could hit, in two acts

The CLI registered a webhook seconds after the dev tunnel printed its URL — before the DNS record existed; the third party negative-cached the NXDOMAIN and refused the hostname for about four minutes. Invisible across four-plus hand rotations, because a human pasting a URL always takes over a minute. Fixed with a readiness gate: two consecutive 200s through the tunnel. Act two, fourteen days and eleven recorded failures later: that gate asked the local resolver, so it failed precisely on machines whose ISP lagged while the rest of the world resolved fine. The gate now asks the world, not the machine it runs on.

Jul 21 → Jul 29

Production-grade agents

The arc that makes an agent something you would put in front of customers.

  • Observability: per-turn traces, a Turn Inspector, agent health endpoints, and OpenTelemetry export — model and tool spans walked as a real Jaeger waterfall on debut day
  • Evals-as-gate plus guardrails: repeat-action guard, daily token budgets
  • Knowledge (RAG) + episodic memory with cited, grounded answers; bring-your-own embedding model and vector store
  • Long-term memory, rolling summarization, prompt caching, budget intelligence — measured live: an 18-turn conversation replayed at 1,451 tokens against ~2,300 before the fold
  • Dashboard live events — 13 pollers retired for 41 WebSocket emit points
  • HITL handoff: agent to human and back, with the handback attributed

from the board · Jul 8 → Jul 24

The fabrication saga — a weak model kept inventing policy

The first managed-brain agents did the one thing an automated support agent must never do: make things up. The fixes arrived as a ladder, each one from a real failure. An anti-fabrication clause went into the tool description itself (Jul 8). Replayed history was rewritten to carry real tool blocks instead of prose breadcrumbs, because prose breadcrumbs could be forged by the model (Jul 9). Then on Jul 24 three incidents landed in a single end-to-end session: the model delivered its own chain-of-thought as the customer reply; with its knowledge base emptied it invented a returns policy; and when the tester simply pushed back — "yes or no?" — it broke again. Each got a detector or a directive plus a regression test.

Jul 30 → Aug 18

The marketing site

A separate repo: a five-scene scroll story that explains the product by animating it.

  • Scene 1 hero: a real damped-pendulum bell, six-channel delivery fan
  • Scene 2 "the engine": pinned schematic of flood, idempotency, retries with a growing backoff ladder, failover, dead-letter queue, priority lanes
  • Install unit in the hero (npm i @asyncify-hq/…), then a first-paint overhaul: 10.3s → 0.4s
  • Engineering budgets held: ~140 of a 150 KB gzip JS budget, ~9.3 of 12 KB CSS; a ~6,000-line scene module guarded by ~155 boot-time asserts run against the real markup
  • Later: a contact page where the form is the product's compose surface (Sep 3), SEO hygiene and share cards (Sep 12), nav rework (Sep 17)

August

from the board · Aug 6, applied through September

A design law, written from a torn animation

The site's two hand-off scenes kept tearing at their seam because one animation computed the other's endpoint arithmetically. The fix was to state the endpoint by reference to the element itself — and the lesson was promoted to a numbered law in the site's design document, then reused every time a later scene had to end where another began. A sibling law came out of the performance work on Aug 17: "the stylesheet owns the first paint". Both exist because a bug was traced to its cause rather than tuned away.

Aug 18 site launch — asyncify.org live

Aug 21 → Aug 26

The quality ladder

The ladder that turns "the agent seems fine" into a measurement.

  • LLM-judge dimensions on eval scenarios: groundedness, tone, refusal
  • A blocking agent-evals gate in CI
  • Prompt versioning + canary — a version trials on a share of real conversations, with a per-arm comparison so "Promote" is a decision
  • Model routing: cheap-first turns, escalation as law
  • Two content gates — an inbound topic gate, an outbound reply-rule gate

Aug 22 the agent-evals gate goes blocking in CI — and catches a real regression on its first run

from the board · Aug 22

The gate that caught its own author, on its first run

The blocking agent-evals gate shipped in CI, and the very first run failed — not on a flake, but because the refund-path scenario had quietly rotted against the current agent. The scenario was rewritten, the second run went green, and the phase closed with the gate "live-proven on debut". Two days later the gate's path list was widened, because the phase that added it had only triggered it through a README file: a change to the conversation processor alone would have skipped a real behavior change.

Aug 26 → Aug 31

Operability and honesty

  • Kill-switch: pause an agent with grace, not a 409
  • Config-as-code: an agent becomes a file — Export / Import / Promote
  • A crashed turn now leaves its trace behind, and the inspector shows how it died
  • Channel honesty and hygiene fixes; the dev tunnel gate asks the world rather than the local machine
  • Scene 5 of the site rebuilt as "the edit" — one prompt change walking through seven stations of the quality ladder

from the board · ~August

The 87-day sweep

The first design for draining stale conversations would have taken ~87 days to clear five million rows — per-conversation delayed jobs, rejected on the spot as per-user-per-event work. It shipped instead as one set-based SQL sweep (SKIP LOCKED, 5,000 rows a batch, a 55-second drain budget) that clears the same backlog in one or two ticks. Then the knob was re-cut from hours down to minutes — so the end-to-end test runs through the real product on one real minute of silence, instead of faking a clock.

September

Sep 5

Production hardening

One day of finding everything that only breaks outside a laptop.

  • The Docker image could not actually be built: NODE_ENV=production above npm ci omitted the runtime's own TypeScript runner, and workspaces were never copied. Both fixed with the reason written into the comment
  • ws://localhost:3001 was hardcoded in three places — the WebSocket origin is now derived from where the page is served, and dev proxies the exact prefix prod uses
  • A production preflight that refuses to boot on dev defaults: weak or shared JWT_SECRET, missing credential encryption key, webhook signing disabled, SSRF escape hatch left open
  • The production compose stack: one box, one hostname, zero published ports; ingress only through an outbound named tunnel
  • DEPLOYMENT.md rewritten from aspiration into the runbook that matches

Sep 6 production deploy day — app.asyncify.org live, 16/16 E2E steps verified

Sep 6

Deploy day

Live at app.asyncify.org on a single €5.50-a-month VPS: compose, a permanent named tunnel, one hostname, zero published ports.

  • All 16 end-to-end steps user-verified, including a cold-reboot self-raise in 43 seconds
  • Two defects were found only by the real box: the Node 20 base crashed every entrypoint at require time, and the dashboard image had to build without the workspace type gate

Sep 7

Dashboard polish and continuous deployment

  • The agent editor split into Edit / Guardrails / Cost & routing / Memory — four tabs over one form, so a half-typed field never dies on a tab switch
  • One house dropdown everywhere: a custom Select replaces all 19 native ones
  • Merging to production now ships app.asyncify.org via GitHub Actions; PR #23 was the first automated deploy — merge to live in 3m47s

Sep 7 continuous deployment — merge to production ships the product

Sep 7 → Sep 8

Security hardening

Three parallel audit sweeps, then seven slices, with riders to Sep 13.

  • The operator plane: global writes stop accepting tenant keys
  • Abuse brakes: per-IP rate limits, per-tenant token keys, scoped provider webhooks
  • Rate-limit brakes on every auth door: login 10 / signup 3 / refresh 30 per minute per IP; widget 60/min with 20 turns/min per subscriber
  • Dependency highs cut 10 → 1 (root) and 2 → 0 (dashboard); an enumeration oracle closed, scrypt raised to N=65536, logs masked
  • "Continue with Google" — a server-side OIDC redirect flow

Sep 8 → Sep 13

The product's front of house

  • Invite-gated beta access: request → approve → invite → gated signup
  • Auth pages redesigned as one designed object, with receipts falling out of the brand bell
  • First-run guided tour (seven stops) and the one-time reveal of the two provisioned API keys
  • Refresh-token rotation with a theft alarm, and a logout that means it — legacy tokens grandfathered by deriving an id from each token's own hash: zero forced re-logins, and no skeleton key minted
  • Operator account suspension: revoke, every door shut, restore with data intact

from the board · Sep 13

"Authenticating a route is not scoping its data"

Every tenant's Overview page was showing the global dead-letter count. The route was authenticated; it simply was not scoped. The fix added a tenant-scoped stats endpoint and gated the platform telemetry stream to the operator seat at the source — and the one-line law went into the engineering ledger so the next route inherits it. It was found by the product's own owner reading his dashboard, which is also how the refresh-token hole was found: a question about what logout actually does exposed that it cleared local storage and nothing else, and rotation with a theft alarm shipped the following day.

Sep 14 → Sep 16

The launch film

A five-act animated film (~64s) built in code, iterated to v15 across 42 commits.

  • The failure field, the burst, the workflow executing, one agent answering back on every channel, and the production harness enforced on camera
  • A synthesized sound layer and then a music bed were both built and then deliberately removed — it ships silent

Sep 16 launch film finished — five acts, ~64 seconds

The ledger — every dated milestone

Jul 5
First commit — the multi-channel notification engine
Jul 5
Multi-tenant foundation: accounts, orgs, environments, API keys
Jul 5
Node SDK + React inbox widget (Phase D)
Jul 6
Rebrand to Asyncify / the @asyncify-hq npm org
Jul 6
Publish prep: dual ESM+CJS builds; first packages go out
Jul 7
First test suite — 51 tests
Jul 7
The agents track opens: the two-way conversation bridge
Jul 8
Telegram becomes the first outside agent channel; email follows
Jul 8
Managed LLM brains — zero-code agents
Jul 8
LLM tool use, with an anti-fabrication clause in the tool contract
Jul 10
CI on every push
Jul 10
Token-less publishing live: Changesets + OIDC trusted publishing (PR #1)
Jul 12
SSRF guard on tenant-supplied outbound URLs
Jul 12
Slack joins as the third agent channel
Jul 13
@asyncify-hq/[email protected] — the agents track's fourth package
Jul 18
Push & SMS hardening; native mobile SDK verified on a real device
Jul 19
@asyncify-hq/[email protected] published (five packages live)
Jul 21
Agent observability: turn traces + the Turn Inspector
Jul 24
Knowledge (RAG) + episodic memory, with cited answers
Jul 25
Long-term memory, rolling summarization, prompt caching, budgets
Jul 27
Human-in-the-loop handoff: agent → human → agent
Jul 30
First commit of the marketing site
Aug 17
Site first paint cut from 10.3s to 0.4s
Aug 18
Site launch — asyncify.org live
Aug 21
LLM-judge scoring lands on evals
Aug 22
The agent-evals gate goes blocking in CI — and catches a real regression on its first run
Aug 22
Prompt versioning + canary trials on live conversations
Aug 23
Model routing: cheap-first turns with escalation
Aug 26
Kill-switch + config-as-code (an agent becomes a file)
Sep 5
Production preflight: the app refuses to boot on dev defaults
Sep 6
Production deploy day — app.asyncify.org live; 16/16 E2E steps verified
Sep 7
Continuous deployment: merge to production ships the product
Sep 8
Security hardening S1 ships; "Continue with Google" goes live
Sep 10
Production becomes an invite-gated beta
Sep 12
Refresh-token rotation with theft alarm; logout that revokes
Sep 13
Cross-tenant data leak found and closed; operator account suspension ships
Sep 16
Launch film finished — five acts, ~64 seconds

Built in 75 days. Running right now.