{"slug": "i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of", "title": "I Shipped a Multi-Tenant Flutter SaaS Overnight — Without Writing a Single Line of App Code", "summary": "A developer shipped a multi-tenant Flutter SaaS application — a futsal-pitch booking system with consumer and operator interfaces, QR check-in, and Firebase backend — in a single overnight session without writing any app code. The project used five parallel Claude Code agents orchestrated by Hermes Agent, each owning a separate directory in a shared monorepo, with the entire development timeline running from 19:38 to 03:09. The orchestration relied on goal-files dispatched to each agent pane, marker files for completion signaling, and a watcher that triggered APK builds and verified deployments.", "body_md": "*This is a submission for the Hermes Agent Challenge*\n\nI shipped a multi-tenant Flutter SaaS — a futsal-pitch booking app with a consumer app, an operator admin console, QR check-in, walk-in POS, and a deployed Firebase backend — in one overnight sitting. The git timeline runs from **19:38 to 03:09**. I never typed app code.\n\nI drove **four (then five) Claude Code agents in parallel**, each in its own pane, each owning one directory of a shared monorepo. The conductor was ** Hermes Agent** (Nous Research, MIT). The visible substrate was\n\nThis is the **Write** category. So this is not a product tour. It is the orchestration teardown: the patterns that let N agents grind one repo at once without colliding, how they signal \"done\" without anyone polling, and how a fresh build lands on a phone seconds after it compiles. Every claim below traces to a commit, a file on disk, or a Hermes skill. Pointers at the bottom.\n\nThe basic architecture diagram is given bellow,\n\nFour panes, one repo, one conductor. No agent touches another's directory.\n\nThen the magic of the markerfile for pingback loop.\n\nConcurrent commits, from `docs/timeline.md`\n\n(the wiki pane logs every commit, which agent, why):\n\n```\n## 2026-05-30 02:43–02:44 — Email/Password + PRD-007/008 backend callables\n   AGENT: backend claude (aa54dc9, 10dc2c6) + frontend claude (fc20bf2).\n## 2026-05-30 00:21–00:28 — FirebaseApi wiring + multi-tenant data model\n   AGENT: frontend claude (10beb79, 236fe01) + backend claude (dacb833)\n          — landed concurrently with this docs upgrade.\n```\n\nThe watcher catching an APK build and a \"done\" ping in the same window:\n\n``` php\n[03:06:10] APK INSTALL: app-arm64-v8a-release.apk -> Success\n[03:07:10] frontend-done: auth errors now show the real code, not generic text...\n           APK rebuilt: /tmp/futsal-frontend-apk; cold-launch->/signin verified.\n```\n\n`github.com/morsheded/futsal-booking`\n\n`github.com/NousResearch/hermes-agent`\n\n`assets/futsal-watcher-redacted.sh`\n\n**Orchestration:** Hermes Agent (conductor) · Herdr (visible terminal substrate) · Claude Code CLI ×5 (the workers). **App:** Flutter 3 / Dart 3 · Riverpod · go_router · Firebase (Auth + Firestore + Functions v2 `asia-south1`\n\nnode22 + Hosting + Storage). **Delivery:** Tailscale (wireless `adb`\n\n) · `mobile_scanner`\n\n(web QR). **Memory:** self-hosted Honcho (`localhost:8000`\n\n, Ollama-backed, Colima + Docker).\n\nThis is the whole mental model. Hermes does not implement features. Hermes writes a **goal-file** per pane, dispatches it into a Claude Code instance, arms a watcher, reads the result marker, and reseeds the next goal. The Claude panes do every line of Dart and TypeScript.\n\nA real goal-file (`/tmp/goal-frontend.txt`\n\n, trimmed):\n\n```\nGOAL: ship PRD-006 (auth routing fix + email/password) on the CLIENT FLUTTER APP only.\nYour turf is `app/` and `packages/gameon_core/`.\n\nREAD FIRST:\n- docs/prd/PRD-006-auth-routing-fix-and-dual-login.md (your spec)\n- app/CLAUDE.md (rule 0: never `git add -A`)\n\nPRECONDITION: backend claude is enabling Email/Password provider in Firebase Auth in\nparallel. If sign-in fails with `operation-not-allowed`, the provider isn't on yet —\nwait, retry. Do NOT mock; this is real Firebase.\n\nWHEN DONE (truly idle, not between subtasks):\n  echo \"<one-line: what shipped + recommended next>\" > /tmp/futsal-frontend-done\n```\n\nThe goal-file is self-contained: identity, turf, what to read, the cross-pane precondition, and the exit contract. Hermes is the only thing holding global state. Each pane holds only its lane.\n\nUnlike `tmux`\n\n, Herdr is purpose-built for agents. Every pane is addressable by ID. Every pane reports a status (idle / working / blocked) into the status bar. Hermes pipes commands in from outside, and I watch the whole thing in real time in my own attached terminal. The `herdr-cli`\n\nskill is how Hermes drives it:\n\n```\nherdr pane run $PANE \"cd /path/to/project && claude --dangerously-skip-permissions\"\nsleep 5                                  # let the TUI draw\nherdr agent send $PANE \"$(cat /tmp/goal-frontend.txt)\"\nherdr pane send-keys $PANE Enter         # agent send does NOT submit — Enter is mandatory\n```\n\nThat missing `Enter`\n\nis the #1 \"I sent it but Claude isn't doing anything\" bug. It is in the skill in bold because it bit us twice.\n\nEach pane owns a directory. That is the entire contract:\n\n| Pane | Owns |\n|---|---|\n| frontend |\n`app/` , `pubspec.yaml` , `pubspec.lock`\n|\n| backend | `backend/` |\n| wiki |\n`docs/` , `wiki/` (maintenance loop) |\n| admin |\n`apps/admin/` , `packages/gameon_core/`\n|\ne2e (added when Playwright landed)\n|\n`e2e/` , `.github/workflows/e2e.yml`\n|\n\nThe hard rule, repeated verbatim as **rule #0** in every pane's `CLAUDE.md`\n\n(this is from `app/CLAUDE.md`\n\n):\n\n```\n0. NEVER `git add -A` or `git add .` from repo root. This is a multi-agent monorepo\n   with 3 other Claudes (backend, wiki, admin) writing to `main` in parallel + an\n   auto-commit hook. Scope every commit to YOUR files only: `git add app/ pubspec.yaml\n   pubspec.lock`. Before commit run `git status --short` and verify nothing outside your\n   turf is staged. If you see foreign paths, that's another agent's WIP — leave it alone.\n```\n\nWhy it matters: if agent A runs `git add -A`\n\nwhile agent B has uncommitted WIP in `wiki/`\n\n, A sweeps B's files into A's commit. Now history says `feat(backend): X`\n\nbut the diff contains wiki edits, and B comes back to a \"clean\" tree and gets confused. Stage explicit paths, never `-A`\n\n, and the entire class of collision bugs disappears. The `multi-claude-monorepo-discipline`\n\nskill is one page and that one rule is the whole cost of parallelism.\n\nDiagram:\n\n[marker-file pingback loop](Excalidraw).\n\nThis is the signal layer. Reading a pane's TUI is noisy — ANSI redraws, partial output. So agents don't get polled. Each agent writes **one line** to its own marker file the moment it goes idle:\n\n```\necho \"<what shipped + recommended next>\" > /tmp/futsal-frontend-done\n```\n\nHermes arms a background watcher that does nothing until the file exists:\n\n```\n# Hermes runs this with background=true, notify_on_complete=true, timeout=86400\nwhile [ ! -f /tmp/futsal-frontend-done ]; do sleep 20; done\nMSG=$(cat /tmp/futsal-frontend-done)\nrm -f /tmp/futsal-frontend-done          # delete immediately, so the next task can re-ping\necho \"FRONTEND CLAUDE IDLE: $MSG\"\n```\n\nWhen the marker appears the loop exits, `notify_on_complete=true`\n\nfires the stdout back into the parent Hermes chat as a system message, and Hermes relays the one-liner to me. The signal is **exactly what the agent decided was done** — it authored it. Zero polling, zero context churn. One unified watcher (`/tmp/futsal-watcher.sh`\n\n) loops all five markers plus the APK marker:\n\n```\nfor f in frontend backend admin e2e wiki; do\n  MF=/tmp/futsal-$f-done\n  if [ -f $MF ]; then\n    MSG=$(cat $MF); rm -f $MF\n    echo \"[$(date +%H:%M:%S)] $f-done: $MSG\" | tee -a $LOG\n  fi\ndone\n```\n\nMy Samsung S24 Ultra lives on my tailnet at `100.101.80.104`\n\n, with `adb`\n\npinned to port `5555`\n\nonce over USB (`adb tcpip 5555`\n\n). When the frontend pane finishes a build it writes the `.apk`\n\npath to a marker, and the same watcher installs it:\n\n```\nif [ -f /tmp/futsal-frontend-apk ]; then\n  APK=$(cat /tmp/futsal-frontend-apk); rm -f /tmp/futsal-frontend-apk\n  adb connect 100.101.80.104:5555 >/dev/null 2>&1\n  adb -s 100.101.80.104:5555 install -r \"$APK\"\nfi\n```\n\nBuild finishes → phone has it in seconds. No USB cable, no Telegram (the bot caps uploads at 50MB; a debug APK is ~144MB anyway — release split-per-abi is ~18MB and fits, but the cable still beats the chat). The log shows the full loop:\n\n```\n✓ Built build/app/outputs/flutter-apk/app-arm64-v8a-release.apk (18.2MB)\nalready connected to 100.101.80.104:5555\n[20:26:57] installing app-arm64-v8a-release.apk to 100.101.80.104:5555...\nSuccess\n[20:27:13] ✓ batch #1 installed on phone (17M)\n```\n\nNothing gets built without a written intent. Every feature has a numbered PRD in `docs/prd/`\n\n(PRD-001 through PRD-009 shipped this run). Hermes writes the PRD *before* dispatching any pane, and the PRD is the first thing the goal-file tells the agent to read. PRDs reference prior PRDs and ADRs, so the system stays coherent without anyone holding the design in their head. The `docs/`\n\ntree is the authoritative spec; `wiki/`\n\nis the auto-derived \"what currently exists\" view the wiki pane regenerates from git. The point, from the `venture-prd-workflow`\n\nskill: **the project must be rebuildable from docs/ alone — even after a full stack swap.**\n\nThis is the pattern almost nobody is running yet. Long-lived Claude panes degrade as their context window fills. My rule: after a major feature ships, when a pane crosses **~300k tokens**, force the `mattpocock/handoff`\n\nskill — compact the conversation into `/tmp/futsal-<pane>-handoff.md`\n\n, then `/exit`\n\n. Next time that pane is needed, relaunch a **fresh** Claude and seed it with the handoff doc.\n\nThe backend pane hit the threshold right after shipping the PRD-006/007/008 callables. Its handoff doc (`/tmp/futsal-backend-handoff.md`\n\n, 64 lines) is a clean state transfer — current deployed state, source-of-truth file pointers, flagged decisions, gotchas, and the exact rule that protects it:\n\n```\n## Commit discipline (HARD RULE — see app/CLAUDE.md rule #0)\n`main` is shared by 4 Claudes + an auto-commit hook. NEVER `git add -A`/`.`/`**`.\nStage explicit `backend/` paths only... If a collision happens anyway, don't rewrite\nshared history — note it in /tmp/futsal-backend-done. (Last time `git add -A` swept the\nwiki Claude's WIP into a backend commit — don't repeat.)\n```\n\nA fresh pane seeded with this doc has the *facts* without the *drift* of a 300k-token conversation. Compaction-induced hallucination on long autonomous runs basically stops.\n\nBest part: I have the rule catching a real collision, captured live in an artifact. The admin pane went to commit and found the e2e pane's WIP already staged in the shared index. It didn't sweep it. It reported it in its own done-marker (`/tmp/futsal-watcher.log`\n\n, `03:05:17`\n\n):\n\n```\n[03:05:17] admin-done: ... commit 832f9cf scoped to apps/admin/ + packages/gameon_core/ ONLY.\n  DEVIATIONS / NOTES:\n  - MULTI-AGENT: the e2e claude's staged e2e/ files were already in the shared index\n    when I went to commit; I git-restored them out so my commit stayed turf-only\n    (apps/admin + packages/gameon_core). e2e/ left untouched for the e2e claude.\n```\n\nThe agent noticed foreign paths, `git restore`\n\n'd them out, committed only its turf, and *narrated the whole thing* so the conductor knew. That is the discipline rule working as a self-healing mechanism — not a guardrail that blocks, but a norm the agent reasons about out loud.\n\n(The repo's history also records the *other* outcome — `dacb833`\n\nin the timeline notes a wide `git add`\n\nthat swept the wiki pane's WIP, with a \"history is not rewritten, noted here\" entry. The rule postdates that commit. The collision happened once, got documented, became rule #0, and then *prevented its own recurrence* at `03:05:17`\n\n. That arc is the actual lesson.)\n\nLast layer. The same Mac runs **three Hermes profiles** — `default`\n\n(the operator that ran this build), `nexus`\n\n(homelab IT), `donna`\n\n(Obsidian second-brain). Each is a full Hermes instance with its own skills, plugins, cron, and memory, and they share one self-hosted **Honcho** memory layer (`localhost:8000`\n\n, workspace `hermes`\n\n, Ollama-backed via Colima + Docker). So the orchestration nests: Hermes drives a fleet of Claude panes for *coding*, and Hermes itself runs as a fleet of profiles for *everything else*. Same multi-agent shape, one level up.\n\nThe speed didn't come from a better model. It came from structure around the model:\n\n`git add -A`\n\nNone of this is model-specific or even Flutter-specific. It is a coordination protocol. Hermes is open source ([NousResearch/hermes-agent](https://github.com/NousResearch/hermes-agent), MIT) and so is the discipline. Steal the patterns.", "url": "https://wpnews.pro/news/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of", "canonical_source": "https://dev.to/morsheded/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of-app-code-184g", "published_at": "2026-05-29 22:12:27+00:00", "updated_at": "2026-05-29 22:42:43.734208+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-products", "ai-startups", "ai-infrastructure"], "entities": ["Claude Code", "Hermes Agent", "Nous Research", "Flutter", "Firebase", "SaaS"], "alternates": {"html": "https://wpnews.pro/news/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of", "markdown": "https://wpnews.pro/news/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of.md", "text": "https://wpnews.pro/news/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of.txt", "jsonld": "https://wpnews.pro/news/i-shipped-a-multi-tenant-flutter-saas-overnight-without-writing-a-single-line-of.jsonld"}}