
那个晚上我对着一个需求改了三次、却要手动跑完测试构建上传的项目终于决定把一个人做全栈这件事彻底重构一遍。过去半年我同时维护着一款工具类Web应用、一个内部管理后台和一套开放API前端用React后端用Node部署靠SSH。说实话累的从来不是写代码本身而是没完没了的上下文切换——上午还在排查后端接口的类型报错下午就得去理前端的状态流晚上还要盯服务器日志。一个人干三个人的活不是知识量不够是精力被切碎了。所以当AI全栈这个概念开始进入视野的时候我并没有盲目追逐AI自动写整个应用这种故事。我更关心的问题是能不能把AI变成真正的队员而不是一个只会输出代码片段的生成器如果把多个Agent、React19、Elysia、DevOps串成一条完整链路一个普通开发者能不能稳定地撑起原本需要一个小团队才能交付的项目带着这个疑问我花了三周时间对三个存量项目做了工程重构把整套研发模式从手写、手测、手部署改成了规范化、Agent协作、自动化发布。这篇文章不是理论推演是我踩完坑之后的完整复盘。如果你是独立开发者、小团队的技术负责人或者正在研究AI辅助开发在真实工程里的落地方式这篇应该对你有用。1. 为什么是一人全栈先聊聊协作方式的改变1.1 传统单人研发的三个真实瓶颈单人全栈最难受的不是某个具体的技术难题而是三件小事。第一是上下文切换。你在写后端路由时脑子里得装着前端怎么消费这个接口回去写前端页面时又得记着后端对边界输入的处理方式。脑力的切换和代码的切换是不同步的经常写完前端忘了后端改到哪或者反过来。一个上午过去可能四个任务都开了头没一个收尾。第二是知识面的深度和广度不可兼得。我自认在前端领域算有经验可到了Nginx配置、Docker镜像优化、数据库索引调优这些领域每跨一次都要临时学几个小时学完又不常用下次还是忘。这种反复进入陌生领域的损耗在小团队里通常被专人专职消化掉了单人全栈则要全部自己扛。第三是时间碎片化。需求方不会因为你是一个人就降低变更频率恰恰相反小项目通常改得更快更频繁。今天改表单校验明天加导出功能后天换服务商每个需求本身不大但打包在一起会切碎你所有整块时间。这三个瓶颈叠加起来的结果是你贡献了大量时间但产出效率非常不稳定。状态好的时候一个人能干两三天的活状态差的时候一整天可能就卡在一个部署问题上而且因为只有你一个人连个可以商量的人都没有。1.2 AI改变的其实是知识获取方式AI进入工程流程之后我最大的感触是它改变的不是写代码这件事——代码本来就能写真正改变的是知识获取方式。以前遇到没碰过的领域流程是查文档、看博客、搜社区、搭Demo、试错、总结。一套流程下来半天到一天起步而且试错成本自己扛。现在用AI辅助我可以直接跳到让AI先给出带理由的方案再由我来判断取舍然后从实验阶段开始。这相当于把从零探索压缩成了从方案到验证。不要理解为AI帮我写所以我不用懂。恰恰相反单人全栈在使用AI时必须比以往更清楚自己想要什么因为你只能依赖自己的判断去验收AI的输出。我给自己定的规矩是把不擅长的领域外包给AI做探索但把这个系统的边界和约束牢牢抓在手里。比如Dockerfile怎么写、CI脚本怎么组织我让Agent帮我生成初版但我必须理解每一行在做什么否则出了线上问题你连排查方向都没有。1.3 多Agent相当于虚拟小团队如果只是用一个AI对话窗口帮你写代码那大概能提速20%到30%但很快会撞到天花板你要不停地把上下文喂给同一个会话它后面的输出会越来越飘修一个bug带出三个新bug最后你得花大量时间给AI擦屁股。这就是我引入多Agent的动机。多Agent不是开多个聊天窗口同时问而是把研发过程中的角色拆开让不同Agent分别扮演架构、实现、审查、运维。它本质上相当于把一个人的精力透过并行计算扩成一个小团队的协作结构。每个Agent拿着明确的任务描述和输入文档输出带明确验收标准的结果再由下一个Agent接力。在这个模式下我本人变成了流程制定者加最终审查者而不是干所有活的苦力。这听起来很酷实操上踩的坑也不少。这里先分享一个核心判断Agent的数量不是越多越好关键是每个Agent的输入、输出、验收标准是否清晰。如果定义不清楚一个Agent会添乱十个Agent就是灾难。2. 技术选型Elysia 是怎么进入这套架构的2.1 后端的选型条件不是最流行是适合一人维护单人重构这套架构时后端框架是我最先定下来的因为它是整个系统的契约源头。我的选型条件很朴素类型系统要强最好能从前端一路吃到数据库运行时依赖要少不要让我在一个环境上折腾半天生态要够用常见能力开箱即用学习成本要低因为我实在没时间看三百页文档。当时我对比了四类主流方案Express、NestJS、Hono和Elysia。Express确实稳生态也大但类型体验几乎为零写接口全靠手测和记忆这对我这种要接AI生成代码的场景非常不友好——没有类型的代码AI生成时也容易跑偏。NestJS的类型和架构都很完整可是装饰器和依赖注入那套心智负担对一个想轻装上阵的单人开发者来说有点重。Hono轻量且跨运行时是个好选择但在类型推导的深度和综合集成度上和Elysia相比还是差一点。最终我选了Elysia。这里多说一句选型不只看性能更要看维护它需要多少脑力。Elysia最打动我的不是技术指标而是它能让我在写后端时保持和写前端一样的类型安全感这直接削减了单人研发里最贵的那部分——跨领域的心智负担。2.2 Elysia全链路类型推导的含金量Elysia是构建在Bun运行时上的TypeScript后端框架最核心的卖点是端到端类型安全。你用Builder模式定义路由和校验Schema框架会从请求参数、响应体到错误处理全程推导类型。这意味着写后端的时候接下一个参数的类型错了IDE立刻标红根本不用等运行时报错。我举个实际例子。任务管理接口的代码如下import { Elysia, t } from elysia; export const taskRoutes new Elysia({ prefix: /api/tasks }) .get(/, async () { return await db.select().from(tasks); }, { response: t.Array(t.Object({ id: t.String(), title: t.String(), done: t.Boolean() })) }) .post(/, async ({ body }) { const result await db.insert(tasks) .values(body) .returning(); return result[0]; }, { body: t.Object({ title: t.String({ minLength: 1 }), done: t.Optional(t.Boolean()) }) });注意这里的response和body都用Schema定义Elysia会同时把它们用作运行时校验和TypeScript类型。你不需要再单独维护一份接口文档——契约的定义和实现是同一段代码。这样带来的直接好处是前端拿到什么类型后端返回什么类型永远一致不存在文档说返回A接口实际返回B的经典悲剧。2.3 一条API契约前端、后端、Agent三端复用Elysia还内置了OpenAPI插件。挂上插件之后框架会自动生成一份openapi.json里面是完整的接口定义。我在前端工程里通过openapi-typescript这类工具把这份JSON转换成TypeScript类型文件直接放进共享包。这条链路解决了我之前最头疼的问题前后端接口对不上。以前我改接口得手动去更新前端的类型声明忘一次就是一堆运行时错误。现在后端Schema一变前端类型文件重新生成IDE直接标红所有受影响的位置。更妙的是这个契约文件对Agent同样有效。我让Agent写前端组件时会直接把生成的类型文件路径丢给它它写出来的fetch调用Body和Response类型基本正确Review时几乎不需要纠正接口调用的问题。对单人研发来说这等于把接口对齐这件事从人工记忆变成了机械自动化。3. React19前端这部分机器替我干了什么3.1 一套表单从七八个useEffect变成一个ActionReact19带来的变化不止于性能更关键的是它把许多以前需要手动管的状态收敛成了框架级能力。对单人开发者来说最直观的收益在表单场景。旧写法是先用useState存表单值再用一堆onChange绑定然后写handleSubmit自己管理pending、error、success三个状态变量。代码勉强能跑但每加一个字段就要改三处稍不注意就是状态不同步的bug。React 19有了Actions和useActionState一个函数就能把提交、pending、校验错误、重置全部包起来。示例代码use client; import { useActionState } from react; import { createTask } from /actions/tasks; export function CreateTaskForm() { const [state, action, pending] useActionState(createTask, { errors: undefined }); return ( form action{action} input nametitle placeholder任务名称 / {state.errors?.title ( p style{{ color: red }}{state.errors.title}/p )} button disabled{pending} {pending ? 提交中... : 创建任务} /button /form ); }配合服务端Action逻辑是这样的use server; export async function createTask(prevState, formData) { const title String(formData.get(title) ?? ).trim(); if (title.length 0) { return { errors: { title: 标题不能为空 } }; } await db.insert(tasks).values({ title }).execute(); return { errors: undefined }; }这套流程省掉了什么不用再写useEffect监听submit状态不用手写loading开关Also不会出现用户连点两次提交导致重复数据的经典并发问题。对单人来说少写一半容易出错的代码就是少花一半时间排查。3.2 useOptimistic先给用户一个好预期省下整套UI状态机后端再快也有网络延迟。React 19的useOptimistic可以在请求发出时立刻在界面上呈现预期结果请求失败再回滚。这看起来只是个体验优化但实际开发中它省掉的是整套loading态回退态的UI状态设计。拿任务列表里的勾选完成操作来说老写法是先改本地state等接口返回后再改一次或者干脆不更新等用户手动刷新。用useOptimistic可以这样写勾选瞬间先把任务的done置为true显示出来接口成功就保持失败就回滚成原状。三种状态集中在一个hook里管理你不用再画什么状态流转图了。我自己的体会是这个特性对AI辅助开发特别友好。过去让Agent写loading、error、success三态组件翻车概率不低——它会想当然地在各种生命周期里塞状态更新经常会漏掉边界情况。有了useOptimistic状态逻辑高度集中Agent一次生成的代码基本能直接用。3.3 React19对Agent写前端的隐藏好处React19还有一个很多人没注意到的变化它整个设计方向都在往声明式和默认行为收敛。ref可以直接作为prop传给函数组件不再需要forwardRef那层包装useEffect被官方文档明确定位为同步外部系统的工具很多以前靠useEffect硬糊的脏需求现在都有了更正规的替代方案。这对Agent编程有很实际的帮助。Agent最擅长的是从声明式描述生成代码——你给我一个组件长得什么样、有什么行为我直接给你写出来。它最怕的是过程式回调地狱——你要按时间顺序描述事件、绑监听、清理监听、处理竞态这种逻辑AI很容易产生幻觉写出一堆看似合理实则多余的代码。所以我把前端代码风格整体迁移到React 19的范式之后最明显的感受不是我写得快了而是Agent写得对——生成代码时出错率下降了一个量级代码审查的部分轻松很多。4. 多Agent编排怎么管理虚拟队员才能不翻车4.1 先想清楚Agent是按工种分还是按任务切我第一次尝试多Agent时犯了一个错误把Agent按功能模块切成写登录模块的Agent、写任务模块的Agent、写仪表盘的Agent。结果非常糟糕——Agent之间互相不了解上下文每个人都在重复搭目录、重复定义接口最后代码风格五花八门合并时冲突一堆。痛定思痛我改成按工种角色划分每个Agent只对一类产出负责交接物和验收标准变得稳定。我的结论是对单人项目来说不要按功能模块切Agent要按研发角色的产出切Agent。模块切法容易形成信息孤岛角色切法才能形成流水线的接力关系。具体原因不复杂按模块切每个Agent都要了解前端、后端、部署三件事才能交付完整模块这对上下文的要求太高Agent很容易半路失控按角色切每个Agent只负责一类任务它可以把全部上下文预算花在把这件事做好上后续Agent又能基于它明确的产出继续接力。4.2 我实际在用的四种Agent角色我的项目里常驻四个Agent角色每个角色有明确的输入、输出和验收标准Agent角色输入输出验收标准架构Agent需求描述、现有代码结构技术方案文档、任务拆解方案可执行、边界明确、改动点列表完整实现Agent技术方案文档、相关文件路径代码Diff通过类型检查、通过相关单元测试审查Agent代码Diff、项目规范文档审查意见、修改建议发现潜在问题并给出可执行的修改建议运维Agent仓库状态、部署日志、监控数据部署报告、故障排查建议发布成功、健康检查通过、异常有明确结论架构Agent的职责是给需求做翻译把需求方的话变成开发者能看懂的技术方案。我通常会把需求描述直接丢给它要求它输出改动哪些文件、有没有涉及数据库变更、需不需要处理兼容性、测试要点有哪些。这份方案是后续所有Agent的输入所以必须有可执行性。实现Agent是最干活的但它不是拿到方案闷头写。我会要求它严格遵循架构Agent给出的文件列表和约束只改该改的地方。它产出的是一份git diff方便我逐行Review。审查Agent的角色最有价值也最容易被忽略。它拿到实现Agent的Diff之后对照项目规范文档检查命名是否符合统一风格有没有复制粘贴的重复代码有没有绕过现有抽象硬写的逻辑。被我设置成发现就问模式宁可误报也不能漏报。运维Agent不常在开发流程里但每次部署和排障时都会用到。它负责看CI日志、检查部署结果、分析监控数据输出一份简短的部署报告告诉我这次发布有没有掩盖的关键风险。4.3 决定协作质量的不是工具是交接文档多Agent编排里工具选谁其实没那么重要——无论你用Claude Code、Codex还是其他支持多会话的AI编程工具核心逻辑都是一样的Agent之间只有文档和代码这两种共同记忆。我实际跑下来有三份文档决定了协作质量。第一份是CONVENTIONS.md也就是项目规范。里面写清楚目录结构、命名风格、模块边界、代码组织方式。每个Agent开工前都先读这份文件确保产出的代码跟既有体系统一。没有它你收获的就是五个程序员独立工作五天拼出来的联邦制代码库风格互相打架。第二份是TASK.md每个迭代的需求描述都被架构Agent整理成这个文件包含背景、目标、改动范围、验收标准、相关文件列表。实现Agent只认这份文件不理解的地方去找架构Agent的注释而不是自己拍脑袋决定。第三份是DEFINITION_OF_DONE.md让所有Agent对什么叫做完有统一认识。我写的版本很简单类型检查必须过、核心路径必须有测试覆盖、改动不能影响既有接口的兼容性、文档有更新。没达到这些标准项目就算代码能跑也不算完成。4.4 实测中最容易翻的车多Agent听起来自动化程度很高但我实测下来翻车点主要集中在这几个地方。第一是过度设计。Agent拿到需求后特别喜欢顺手给你抽象一个可扩展架构于是为了一个三行逻辑拖出三个文件、两个基类、一个工厂函数。补救办法是给实现Agent写清楚保持简单的约束并在审查Agent的检查清单里加一条如果这个新抽象只被用了一次建议直接内联。第二是不遵循既有代码风格。前一个Agent用函数组件后一个Agent给你写class组件前一个用组合式目录后一个按新结构另起一套。这类问题的根源是让Agent读了太多无关文件、没读规范。所以我把CONVENTIONS.md放在每个任务说明的最前面并且要求Agent必须读过规范才能提交。第三是上下文窗口不够导致的摆烂。Agent在上下文不够时不会明确说我处理不了了而是开始给你含糊代码或者省略关键逻辑比如在注释里写// 其余逻辑同上面。对付这个我会把一个大任务拆成多个小步骤每次只给它必要输入。宁可多跑几轮Agent也不要一个Agent一次性干完所有事。5. 单人DevOps把发布变成推送即上线5.1 自动化是单人研发唯一的护城河单人研发最危险的状态是什么是只有你一个人知道怎么发布。如果发布流程依赖你在SSH终端里敲一串手写命令当这个项目放三个月后再捡起来你大概率会对着终端发呆我当初是怎么部署的所以我把DevOps的自动化排在跟业务代码同等重要的优先级上。目标很简单git push之后测试、构建、推送镜像、部署、健康检查全部自动跑完。你不需要记住任何部署步骤只需要记住推送代码这一个动作。这套自动化还有一个隐藏价值它让AI也能执行部署。运维Agent只需要读取CI日志和部署状态就能判断发布是否成功。于是连部署确认这个动作都可以从你的待办清单里划掉了。5.2 最小发布链路需要哪几件东西单人项目不需要K8s那套庞然大物但也别糊一个纯手动脚本。我用的是一条很精简的链路环节组件作用版本控制GitHub私有仓库代码托管、触发CICIGitHub Actions跑测试、构建镜像、执行部署容器Docker Dockerfile保证环境一致、方便回滚部署目标VPS Docker Compose一个轻量可靠的运行环境监控健康检查接口 探活服务确认服务还活着日志集中式日志收集排障不用登服务器翻文件这套组合的好处是每一层都替换成本很低。今天不想用GitHub Actions换个CI平台就是改个脚本明天想从VPS迁到托管平台Docker已经帮你把运行环境固定好了。5.3 CI/CD配置一个可以直接抄的GitHub Actions我的CI配置文件不长但每一步都有讲究name: deploy on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: oven-sh/setup-bunv2 with: bun-version: latest - run: bun install - run: bun test build-and-deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Log in to registry run: echo ${{ secrets.REGISTRY_TOKEN }} | docker login registry.example.com -u ${{ secrets.REGISTRY_USER }} --password-stdin - name: Build and push image uses: docker/build-push-actionv6 with: context: . push: true tags: registry.example.com/myapp:latest - name: Deploy via SSH run: | ssh deploymyserver -i ${{ secrets.SSH_KEY }} docker pull registry.example.com/myapp:latest docker compose --env-file .env.prod up -d curl -fsS http://localhost/healthz 第一段test负责把关所有测试不过后面直接不跑。第二段build-and-deploy里先登录镜像仓库然后构建并推送Docker镜像最后SSH到服务器执行拉取镜像和重新部署的操作部署完立刻探活。核心思路是Docker镜像不变服务器环境就永远一致健康检查不过说明这次发布有问题要准备回滚。配套的Dockerfile我用的多阶段构建把依赖安装和生产构建分开最终镜像只保留运行所需的最小文件。这样镜像小、启动快、攻击面也少。回滚操作也很简单docker compose里保留上一个大版本镜像的标签出问题一条命令切回去即可。5.4 监控告警一个人也要能半夜被叫醒单人开发最大的风险是你根本不知道服务什么时候挂了——用户可能比你发现得早。所以我给每个服务都加了一个/healthz接口返回数据库连接状态和核心依赖是否正常配合探活服务每分钟检查一次。挂了就发通知到手机。这条链路是必须的因为单人没有团队可以互相盯班。日志方面我用了集中式日志方案应用把结构化日志发到统一收集端再配合简单查询面板。排障时不再需要SSH到服务器展开几百MB的日志文件直接在面板里按请求ID搜索。看似多搭了一套组件实际上把单人的排障时间从小时级压到了分钟级。这里有一个被很多人忽略的点自动化要做得尽量静默。如果每次部署都收到一堆通知消息你会很快麻木然后错过真正重要的告警。我把告警分了级别部署成功只在有异常时才通知健康检查失败是紧急级别测试失败不通知自己看CI就行。让告警变得稀有你才会认真对待每一次响声。6. 重构跑完以后我最真实的几点体会这次重构做完最大的收益不是代码行数减少了多少而是失控感大幅减少了。以前最大的焦虑是项目能不能在我离开一周之后仍然正常运转现在答案是肯定的——测试自动化把关、Agent可接力、部署流程一条命令、监控告警兜底。哪怕我真的有一段时间不管系统也能自己维持稳定回来之后看一眼日志就清楚发生了什么。我建议想复制这套范式的朋友不要一次性把全链路都推倒重来。最快的路径是先把后端换成Elysia把类型契约搞定再把前端组件逐步迁移到React 19的范式最后多Agent从审查Agent开始用先让它帮你做代码审查建立起信心之后再加实现AgentDevOps的自动化甚至可以最先做因为它收益最快。小步快跑每一步都能独立验收比一次推翻重构稳得多。最后分享一个实操小技巧给运维Agent写提示词的时候别只让它看日志给它一份排障Checklist让它必须按清单逐项检查——部署状态、健康检查结果、数据库连接、最近错误日志、资源占用。每次故障排查都让Agent走同一套流程坚持一段时间之后你会得到一份越用越准的排障手册。这套一人全栈的范式值不值得至少对我来说它把研发变成了一件可持续的事情。