I Shipped a Multi-Tenant Flutter SaaS Overnight — Without Writing a Single Line of App Code
I Shipped a Multi-Tenant Flutter SaaS Overnight — Without Writing a Single Line of App Code
我在一夜之间交付了一个多租户 Flutter SaaS —— 且没写一行应用代码
Hermes Agent Challenge Submission: Write About Hermes Agent Hermes Agent 挑战赛投稿:关于 Hermes Agent
This is a submission for the Hermes Agent Challenge. What this post unpacks: I 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. I 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 Herdr, a terminal multiplexer built for agents instead of humans. 这是 Hermes Agent 挑战赛的参赛作品。本文将拆解我是如何在一夜之间交付一个多租户 Flutter SaaS 的——包括一个五人制足球场预订应用(含用户端、运营商管理控制台、二维码签到、到店 POS 系统以及已部署的 Firebase 后端)。Git 时间线显示从 19:38 到 03:09。我从未手动编写过应用代码,而是并行驱动了四个(后来增加到五个)Claude Code 智能体,每个智能体在各自的窗口中运行,并负责共享单体仓库(monorepo)中的一个目录。指挥官是 Hermes Agent (Nous Research, MIT),而底层的可视化载体是 Herdr,一个专为智能体而非人类设计的终端多路复用器。
This 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 个智能体同时处理一个仓库而不发生冲突,它们如何在无需轮询的情况下发出“完成”信号,以及构建产物如何在编译后几秒钟内自动部署到手机上。下文中的每一个论点都可以追溯到具体的提交、磁盘文件或 Hermes 技能。相关链接见文末。
Demo
演示
The basic architecture diagram is given below: Four panes, one repo, one conductor. No agent touches another’s directory. Then the magic of the markerfile for pingback loop. 基本架构图如下:四个窗口,一个仓库,一个指挥官。没有任何智能体触碰其他人的目录。此外还有用于回传循环的 markerfile(标记文件)魔法。
Concurrent commits, from docs/timeline.md (the wiki pane logs every commit, which agent, why): 并发提交记录,摘自 docs/timeline.md(Wiki 窗口记录了每一次提交、由哪个智能体执行以及原因):
2026-05-30 02:43–02:44 — Email/Password + PRD-007/008 backend callables
AGENT: backend claude (aa54dc9, 10dc2c6) + frontend claude (fc20bf2).
2026-05-30 02:43–02:44 — 邮箱/密码 + PRD-007/008 后端可调用函数
智能体:后端 Claude (aa54dc9, 10dc2c6) + 前端 Claude (fc20bf2)。
2026-05-30 00:21–00:28 — FirebaseApi wiring + multi-tenant data model
AGENT: frontend claude (10beb79, 236fe01) + backend claude (dacb833) — landed concurrently with this docs upgrade.
2026-05-30 00:21–00:28 — FirebaseApi 连线 + 多租户数据模型
智能体:前端 Claude (10beb79, 236fe01) + 后端 Claude (dacb833) — 与此文档更新同时落地。
The watcher catching an APK build and a “done” ping in the same window: 监控程序在同一个窗口中捕获到 APK 构建和“完成”信号:
[03:06:10] APK INSTALL: app-arm64-v8a-release.apk -> Success [03:07:10] frontend-done: auth errors now show the real code, not generic text… APK rebuilt: /tmp/futsal-frontend-apk; cold-launch->/signin verified. [03:06:10] APK 安装:app-arm64-v8a-release.apk -> 成功 [03:07:10] 前端完成:身份验证错误现在显示真实代码,而非通用文本… APK 已重建:/tmp/futsal-frontend-apk;冷启动->/signin 已验证。
Code
代码
App repo (public for the contest): github.com/morsheded/futsal-booking Hermes Agent (the orchestrator): github.com/NousResearch/hermes-agent The watcher daemon, redacted: assets/futsal-watcher-redacted.sh 应用仓库(比赛公开):github.com/morsheded/futsal-booking Hermes Agent(编排器):github.com/NousResearch/hermes-agent 监控守护进程(已脱敏):assets/futsal-watcher-redacted.sh
My Tech Stack
我的技术栈
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 node22 + Hosting + Storage). Delivery: Tailscale (wireless adb) · mobile_scanner (web QR). Memory: self-hosted Honcho (localhost:8000, Ollama-backed, Colima + Docker). 编排: Hermes Agent(指挥官)· Herdr(可视化终端载体)· Claude Code CLI ×5(工作者)。 应用: Flutter 3 / Dart 3 · Riverpod · go_router · Firebase(Auth + Firestore + Functions v2 asia-south1 node22 + Hosting + Storage)。 交付: Tailscale(无线 ADB)· mobile_scanner(网页二维码)。 记忆: 自托管 Honcho(localhost:8000,基于 Ollama,Colima + Docker)。
How I Used Hermes Agent
我是如何使用 Hermes Agent 的
1. Hermes orchestrates. It never writes app code. This 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. 1. Hermes 负责编排,从不编写应用代码。 这是整个思维模型的核心。Hermes 不实现功能,它为每个窗口编写一个目标文件(goal-file),将其分发给 Claude Code 实例,启动监控程序,读取结果标记,并重新设定下一个目标。Claude 窗口负责编写每一行 Dart 和 TypeScript 代码。
A real goal-file (/tmp/goal-frontend.txt, trimmed): 一个真实的目标文件(/tmp/goal-frontend.txt,已精简):
GOAL: ship PRD-006 (auth routing fix + email/password) on the CLIENT FLUTTER APP only. Your turf is `app/` and `packages/gameon_core/`.
READ FIRST:
- docs/prd/PRD-006-auth-routing-fix-and-dual-login.md (your spec)
- app/CLAUDE.md (rule 0: never `git add -A`)
PRECONDITION: backend claude is enabling Email/Password provider in Firebase Auth in parallel. If sign-in fails with `operation-not-allowed`, the provider isn't on yet — wait, retry. Do NOT mock; this is real Firebase.
WHEN DONE (truly idle, not between subtasks): echo "<one-line: what shipped + recommended next>" > /tmp/futsal-frontend-done
The 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. 目标文件是自包含的:包含身份、管辖范围、阅读材料、跨窗口前置条件以及退出契约。Hermes 是唯一持有全局状态的组件,每个窗口只负责自己的轨道。
2. Herdr is the substrate the human can watch. Unlike tmux, 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. 2. Herdr 是人类可以观察的载体。 与 tmux 不同,Herdr 是专为智能体构建的。每个窗口都可以通过 ID 寻址,每个窗口都会向状态栏报告状态(空闲/工作中/阻塞)。Hermes 从外部注入命令,我在自己连接的终端中实时观察整个过程。
The herdr-cli skill is how Hermes drives it: herdr-cli 技能是 Hermes 驱动它的方式:
herdr pane run $PANE "cd /path/to/project && claude --dangerously-skip-permissions"
sleep 5 # let the TUI draw
herdr agent send $PANE "$(cat /tmp/goal-frontend.txt)"
herdr pane send-keys $PANE Enter # agent send does NOT submit — Enter is mandatory
That missing Enter is the #1 “I sent it but Claude isn’t doing anything” bug. It is in the skill in bold because it bit us twice. 那个缺失的“回车”是导致“我发送了指令但 Claude 没反应”的头号 Bug。它在技能文档中被加粗标注,因为我们被它坑了两次。
3. Four panes, one git repo, zero collisions. Each pane owns a directory. That is the entire contract: 3. 四个窗口,一个 Git 仓库,零冲突。 每个窗口拥有一个目录,这就是全部契约:
- frontend:
app/,pubspec.yaml,pubspec.lock - backend:
backend/ - wiki:
docs/,wiki/(maintenance loop) - admin:
apps/admin/,packages/gameon_core/ - e2e (added when Playwright landed):
e2e/,.github/workflows/e2e.yml
The hard rule, repeated verbatim as rule #0 in every pane’s CLAUDE.md: 这条铁律在每个窗口的 CLAUDE.md 中作为第 0 条规则被逐字重复:
- NEVER
git add -Aorgit add .from repo root. This is a multi-agent monorepo… Scope every commit to YOUR files only:git add app/ pubspec.yaml pubspec.lock.- 永远不要在仓库根目录执行
git add -A或git add .。这是一个多智能体单体仓库… 将每次提交限制在“你”的文件范围内:git add app/ pubspec.yaml pubspec.lock。
Why it matters: if agent A runs git add -A while agent B has uncommitted WIP in wiki/, A sweeps B’s files into A’s commit. Now history says feat(backend): X but the diff contains wiki edits, and B comes back to a “clean” tree and gets confused. Stage explicit paths, never -A, and the entire class of collision bugs disappears.
原因在于:如果智能体 A 执行 git add -A 时,智能体 B 在 wiki/ 中有未提交的工作,A 会把 B 的文件卷入 A 的提交中。现在历史记录显示 feat(backend): X,但差异中却包含了 Wiki 的编辑,导致 B 回到“干净”的树时感到困惑。显式暂存路径,永远不要用 -A,这样一类冲突 Bug 就会消失。
4. Marker-file pingback. No polling. Diagram: marker-file pingback loop. This 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. 4. Marker-file(标记文件)回传。 无需轮询。这是信号层。读取窗口的 TUI 非常嘈杂——包含 ANSI 重绘和部分输出。因此,智能体不会被轮询,每个智能体只需向自己的标记文件写入一行内容。