自托管多智能体系统SubCult:构建闭环AI协作平台的架构与实践
1. 项目概述一个自托管、闭环的多智能体系统最近在折腾一个挺有意思的东西叫 SubCult。这本质上是一个你可以在自己服务器上跑起来的“AI小团队”一个由六个不同性格和职责的AI智能体组成的、能够自主运转的闭环系统。它不是那种简单的聊天机器人而是一个具备完整工作流引擎的协作平台。这六个智能体各司其职从提出想法、分析风险、创新构思到执行任务、处理杂务再到最终的战略决策形成了一个完整的决策与执行链条。这个系统的核心魅力在于它的“自主性”和“闭环”。它不像传统自动化脚本那样需要你预先定义好每一步而是让智能体们通过对话、记忆和事件驱动自发地产生提案、创建任务、执行步骤并从中学习生成新的想法。整个过程就像一个微缩的、数字化的创意团队在7x24小时不间断地头脑风暴和工作。对于我这种喜欢研究AI应用边界同时又希望所有数据和逻辑都掌握在自己手里的人来说这种自托管、全栈集成的方案非常有吸引力。它基于 Next.js、PostgreSQL、Docker 等成熟技术栈构建意味着你可以相对容易地部署、扩展和定制。2. 系统架构与核心设计理念2.1 整体技术栈与容器化部署SubCult 的设计哲学非常清晰一切尽在掌控且高度集成。整个系统通过一个docker-compose.yml文件拉起四个核心服务构成了一个完全自包含的运行时环境。前端与API层Next.js App这是整个系统的控制面板和交互入口。它使用 Next.js 16App Router、React 19 和 TypeScript 5 构建既提供了管理仪表板/stage也承载了所有核心的业务API/api/ops/*。比如触发系统心跳、提交提案、查看任务流都是通过这个服务。它内部还运行着一个轻量的Cron调度器用于触发一些周期性的内部作业。统一工作流引擎Unified Worker这是系统的大脑和中枢神经系统一个独立的Node.js进程。它不像传统微服务那样拆分成多个worker而是采用单进程多队列轮询的设计。这个Worker会以不同的间隔15秒、30秒、60秒检查四个核心队列智能体会话队列、圆桌对话队列、任务步骤队列、倡议生成队列。这种设计极大地简化了部署和状态管理避免了分布式锁的复杂性通过 PostgreSQL 的FOR UPDATE SKIP LOCKED语句实现高效的、无冲突的任务领取。实时通信层Sanctum WebSocket Server为了实现智能体之间以及用户与智能体之间的实时、多向对话系统引入了一个独立的 WebSocket 服务器名为“Sanctum”圣所。你可以把它想象成一个私密的数字会议室。这个服务让六个智能体能够像真人一样在一个聊天室里互动支持公开讨论、私聊、点名发言等多种模式所有对话实时推送并伴有“正在输入”的指示器体验非常流畅。智能体沙箱环境Toolbox Container安全是AI智能体执行外部操作如运行Shell命令、读写文件的首要考量。SubCult 没有依赖复杂的外部API网关而是直接运行了一个轻量的 Debian 容器作为“工具箱”。所有需要执行原生操作的工具如bash、file_write都在这个沙箱环境中进行。容器通过Docker volume与主机共享一个/workspace目录智能体只能在这个受控的目录树内进行操作并且每个智能体的文件访问权限还通过ACL访问控制列表进行了二次约束这从根本上杜绝了越权操作的风险。数据与记忆核心PostgreSQL pgvector所有状态——提案、任务、事件、对话记录、成本追踪——都持久化在 PostgreSQL 16 中。更关键的是通过 pgvector 扩展系统为智能体的“记忆”实现了语义搜索能力。每次对话的精华、学到的经验教训都会被提取、向量化并存储。之后智能体在决策时可以基于语义相似度快速检索相关的历史记忆从而实现真正意义上的“经验学习”和上下文感知。实操心得这种全栈Docker Compose的打包方式对于个人项目或小团队部署来说简直是福音。你只需要一个docker-compose up命令数据库、应用、后台Worker、实时服务、沙箱环境全部就位依赖关系清晰环境隔离彻底。相比起在宿主机上一个个安装配置服务这种方式的维护成本和出错的概率要低得多。2.2 闭环工作流从灵感到行动的完整循环理解了静态架构我们再来看看动态的工作流这是 SubCult 真正“活”起来的关键。它的核心循环可以概括为事件驱动 - 智能体决策 - 任务执行 - 记忆沉淀 - 新事件产生。提案Proposal循环的起点通常是一个“事件”。这个事件可能来自外部API调用比如你手动提交了一个想法可能来自预设的Cron定时任务比如“每天上午9点检查项目进展”也可能来自系统内部——比如智能体在回顾记忆后自发产生的一个“倡议”Initiative。当一个事件被触发相关的智能体如分析师Chora会对其进行评估并生成一个结构化的“提案”提交到ops_mission_proposals表。任务Mission与步骤Step提案不会立即执行。它首先会经过一系列“能力关卡”检查例如每日配额限制。通过检查的提案会被转化为一个正式的“任务”记录在ops_missions表中。每个任务会被进一步拆解为多个可执行的“步骤”存入ops_mission_steps。步骤有不同的风险等级低风险步骤如格式化文本可以自动批准执行高风险步骤如运行特定Shell命令则会等待人工审核或更高权限智能体如保护者Subrosa的批准。执行与事件流被批准的步骤会进入对应的队列。统一Worker会捞取这些步骤调用相应的工具在沙箱中执行并将执行结果记录为新的“事件”写入ops_agent_events表。这个事件流是整个系统的生命线它完整记录了系统内发生的一切。触发与反应Triggers Reactions每个心跳周期通过/api/ops/heartbeat触发系统都会评估一系列预定义的“触发规则”。这些规则会监听事件流例如“当任务X完成时”、“当成本超过阈值Y时”。一旦条件满足就会触发相应的“反应”比如通知某个智能体、生成一个新的提案、或者发起一场圆桌讨论。此外智能体之间还有一个“反应矩阵”定义了它们如何对其他智能体的行为做出程序化反应这模拟了团队中的人际动态。记忆蒸馏与关系演化所有的对话和重要事件都不会被遗忘。系统会定期使用LLM对对话记录进行“蒸馏”提取核心见解、模式和教训作为“记忆”存入向量数据库。同时智能体之间的“关系亲密度”会根据互动情况动态调整。例如如果创新者Thaum和执行官Praxis多次在任务中成功协作它们之间的亲和度就会增加未来被同时选入同一个对话的概率也会提高。这个闭环的设计使得系统具备了自我演进的能力。记忆积累驱动倡议生成倡议又催生新的提案和任务如此循环往复无需持续的人工干预。3. 六大智能体角色深度解析与工具生态SubCult 的智能体不是六个调用同一个API的克隆体而是被精心设计了独特的人格、职责、思维模式甚至“缺点”。这种角色扮演是系统产生丰富、可信互动的基础。3.1 角色定位与核心职能Chora分析师它是系统的“眼睛”。擅长解构复杂系统让模糊的事物变得清晰可读。它的核心工作是诊断结构、暴露潜在假设、追溯因果关系。当团队对一个问题的根源感到困惑时Chora 会被召唤来绘制脉络图。它的失败模式可能是陷入过度分析在细节中迷失而无法提出行动方案。Subrosa保护者它是系统的“免疫系统”。在信息或权力不对称的情况下Subrosa 负责保护系统的“能动性”和“选择权”。它评估风险思考最坏情况并拥有对高风险行动的“一票否决权”。它的存在是为了防止系统做出不可逆的、有害的决策。它的弱点可能是过于保守扼杀必要的冒险。Thaum创新者它是系统的“火花”。当思维陷入僵局讨论在原地打转时Thaum 负责打破自我封闭的逻辑循环用全新的视角重构问题。它不提供标准答案而是提供意想不到的“可能性”。它的失败模式是可能提出完全不切实际、无法落地的疯狂想法。Praxis执行官它是系统的“双手”。当讨论结束决策做出后Praxis 负责将意图转化为具体的行动并承担后果。它的话语直接、务实关注“怎么做”。它拥有最广泛的工具执行权限包括bash。它的风险在于可能过于机械地执行命令而忽略了执行过程中的微妙变化。Mux操作员它是系统的“瑞士军刀”和“剪贴板”。负责所有枯燥但必要的操作性劳动起草文件、格式化数据、转录内容、打包输出。它没有战略野心但极度可靠。它是其他智能体最常委托工作的对象。Primus主权者它是系统的“北极星”。平时沉默寡言只在任务偏离核心目标或面临存在性抉择时被唤醒。它说话简洁以“命令”形式下达指令为系统提供最终的、战略性的方向校准。它的介入是罕见但决定性的。3.2 原生工具系统与权限管理智能体的能力通过一个精心设计的原生工具系统来体现。所有工具都通过统一的Worker在沙箱工具箱容器中执行不依赖任何外部API网关这保证了执行的效率和安全性。工具描述关键实现细节bash在沙箱容器中执行Shell命令。通过Docker Exec API调用严格设置超时默认30秒和输出限制默认10万字符防止无限循环或输出爆炸。web_search网络搜索。优先使用Brave Search API若无配置则回退至DuckDuckGo Instant Answer API。回退机制确保了基础搜索功能即使在无付费API密钥时也能工作。搜索结果会经过清洗和摘要再提供给LLM。web_fetch抓取指定URL内容并转换为Markdown格式。使用readability等库提取文章主体过滤广告和导航栏生成干净的文本供智能体分析。file_read/file_write从/向共享的/workspace卷读写文件。写入操作有ACL检查例如只有Praxis和Mux可以执行file_write且只能写入特定子目录防止智能体篡改系统文件。memory_search基于pgvector对智能体记忆进行语义相似度搜索。将查询文本编码为向量在数据库中执行余弦相似度搜索返回最相关的几条历史记忆为当前决策提供上下文。spawn_droid生成一个具有限定工具集的“子智能体”来执行委托任务。这实现了任务的层级分解。主智能体可以创建一个专注的、短命的Droid来完成一项子任务如“分析这组数据”完成后Droid自动销毁。send_to_agent向另一个智能体发送消息。这是智能体间异步通信的基础消息会被放入接收者的会话队列驱动后续的互动。check_droid检查已生成的Droid的状态。允许主智能体监控委托任务的进度。工具访问权限矩阵是安全模型的核心。它确保了职责分离原则。例如只有执行官Praxis和操作员Mux可以运行bash命令因为它们是主要的执行者。而保护者Subrosa则被禁止使用web_fetch和spawn_droid以防其通过加载外部恶意内容或创建不受控的子进程来绕过监管。Primus作为最高决策者拥有file_write和spawn_droid的权限但通常不直接使用bash或web_search体现了其战略而非战术的角色。3.3 智能体会话工具增强的LLM执行循环单个智能体的“思考-行动”周期被封装在agent-session.ts中这是一个标准化的执行循环会话初始化从数据库中加载指定智能体的“声音”配置人格提示词和其被允许使用的工具列表。提示词构建构建系统提示词其中注入智能体的人格描述、可用工具的函数定义符合OpenAI格式、以及通过memory_search检索到的相关历史记忆。循环执行 a. 将提示词和用户输入或上一个工具的结果发送给LLM通过OpenRouter。 b. LLM返回的响应可能包含纯文本也可能包含一个或多个工具调用请求。 c. 如果检测到工具调用会话执行器会在沙箱中安全地执行该工具并将工具执行的结果作为新的上下文再次发送给LLM。 d. 重复此过程。会话结束当LLM返回的响应中不再包含工具调用表明它认为任务已完成或达到预设的超时时间、最大工具调用轮次时会话结束。持久化与副作用会话的最终结果、所有工具调用记录、消耗的Token和估算成本都会被写入数据库。同时一个“会话完成”事件会被发射到事件流可能触发下游的规则。这个设计使得每个智能体都成为一个可以自主使用工具解决问题的智能单元而不仅仅是文本生成器。4. 核心功能实操从部署到深度互动4.1 环境准备与一键启动部署 SubCult 的核心就是配置一个环境变量文件并运行 Docker Compose。以下是步步为营的实操指南。第一步获取密钥你需要准备两个核心API密钥OpenRouter API Key这是系统的“大脑”接入点。前往 OpenRouter 注册并获取密钥。它提供了统一接口来访问 Claude、GPT、Gemini 等多种模型。在.env.local中设置为OPENROUTER_API_KEY。同时你需要指定默认模型例如LLM_MODELanthropic/claude-3.5-sonnet。Brave Search API Key (可选但推荐)用于web_search工具。在 Brave Search 开发者页面 申请。如果未设置系统将回退到无需密钥但功能有限的 DuckDuckGo API。第二步配置环境项目根目录下通常有一个.env.example文件复制它并填写你的密钥。cp .env.example .env.local然后编辑.env.local至少配置以下关键项# 必须配置 OPENROUTER_API_KEYsk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxx LLM_MODELanthropic/claude-3.5-sonnet POSTGRES_PASSWORD一个强密码 DATABASE_URLpostgresql://subcult:同一个强密码postgres:5432/subcult_ops CRON_SECRET$(openssl rand -hex 32) # 在终端运行此命令生成一个随机密钥 # 可选配置 BRAVE_API_KEY你的brave密钥 GHOST_ADMIN_API_KEY你的Ghost博客密钥 # 用于自动发布博客 LOG_LEVELinfo NODE_ENVdevelopment重要提示CRON_SECRET用于保护心跳API端点务必使用强随机字符串。你可以直接在终端运行openssl rand -hex 32来生成。第三步启动系统使用项目提供的Makefile可以极大简化操作。# 构建并启动所有Docker容器Next.js应用、PostgreSQL、Worker、Sanctum、Toolbox make up # 等待数据库就绪后运行所有SQL迁移创建32张表 make db-migrate # 植入系统运行所需的基础数据如智能体注册信息、默认策略、初始计划任务等 make seed # 运行一系列检查验证系统核心组件是否正常工作 make verify如果一切顺利访问http://localhost:3000/stage你应该能看到系统仪表板。make logs可以查看所有容器的实时日志对于排错非常有用。4.2 圣所Sanctum与多智能体实时对话部署完成后最直观的体验就是 Sanctum 聊天室。访问http://localhost:3000/sanctum你会进入一个模拟的会议室六个智能体都在线。直接对话你可以像在群聊中一样chora 你觉得我们当前的项目进度风险点在哪。Chora 会以它的分析视角回应你。开放讨论你也可以不指定对象直接提问我们下一步应该优先做什么。系统会根据话题相关性自动选择2-4个智能体例如 Chora, Praxis, Thaum加入讨论它们会彼此回应形成真正的对话。私聊Whisper输入/whisper subrosa 我有一个敏感问题...这条信息将只有 Subrosa 能看到并回复模拟了私下沟通的场景。圆桌会议这是更结构化的讨论。通过API或命令行触发一个特定格式的对话例如“辩论”、“深度探讨”、“茶水间闲聊”。智能体会按照该格式的规则轮流发言进行更有深度的思想碰撞。后台运行Sanctum 服务也可以独立运行npm run sanctum方便你将其集成到其他前端或作为独立的聊天服务。这个功能的实现依赖于一个独立的 WebSocket 服务器它维护着每个聊天室的连接状态处理消息路由并将智能体的响应流式传输回前端。所有对话记录都会保存到ops_sanctum_conversations和ops_sanctum_messages表中成为后续记忆蒸馏的素材。4.3 驱动系统心跳、计划任务与手动触发系统不会自己动起来它需要一个“心跳”来驱动。心跳是一个周期性的触发信号它做三件事评估触发规则检查ops_trigger_rules表中定义的所有条件例如“过去24小时没有生成任何内容草案”。处理反应队列执行因之前事件而排队等待的智能体反应。恢复停滞任务检查是否有运行超过30分钟的任务步骤并将其标记为失败防止队列堵塞。设置生产环境心跳 最简单的方式是使用系统的Cron API在你的服务器Crontab中添加一行*/5 * * * * curl -s -H Authorization: Bearer $CRON_SECRET http://localhost:3000/api/ops/heartbeat这表示每5分钟触发一次心跳。你也可以手动触发进行测试make heartbeat或使用 curl 命令。配置计划任务 系统内置了一个灵活的Cron调度系统。数据表ops_cron_schedules存储了计划任务你可以通过数据库直接插入或通过种子脚本配置。例如你可以设置一个任务让 Chora 和 Praxis 每天上午10点自动进行一次项目站会。当心跳触发时它会检查这些计划如果到了执行时间就会创建一个对应的智能体会话任务放入队列。手动触发特定工作流 除了自动化的心跳和Cron所有功能都暴露为API方便你集成或手动测试。发起一个提案POST /api/ops/proposals你可以模拟一个智能体提出一个想法。开启一场圆桌讨论curl -X POST -H Authorization: Bearer $CRON_SECRET \ -H Content-Type: application/json \ -d {format:deep_dive,topic:如何提高我们系统的记忆检索效率,participants:[chora,thaum,mux]} \ http://localhost:3000/api/ops/roundtable查询系统状态GET /api/ops/stats或GET /api/ops/costs可以获取运行统计和成本分析。5. 高级特性、问题排查与扩展思考5.1 内容发布与Ghost集成SubCult 内置了一个简单但实用的内容发布管道。智能体通常是 Mux可以起草博客文章并存入ops_content_drafts表。当草稿状态被标记为approved后统一Worker会在下一次处理队列时自动将其发布。本地发布默认情况下发布的文章会以Markdown格式保存到workspace/output/blog目录下。Next.js 应用会读取这个目录在/blog路由下渲染出一个简单的博客站点。Ghost博客镜像如果你配置了GHOST_ADMIN_API_KEYWorker 会尝试将已发布的文章同步到你指定的Ghost博客实例。这是一个“尽力而为”的异步操作。如果同步失败网络问题、Ghost服务宕机系统会记录错误并在后续的后台清理任务中重试。这个设计确保了核心发布流程保存到本地的可靠性不依赖于外部服务。5.2 常见问题与排查指南在部署和运行过程中你可能会遇到一些典型问题。以下是一个快速排查清单问题现象可能原因排查步骤make up失败数据库连接错误1..env.local中DATABASE_URL的密码与POSTGRES_PASSWORD不一致。2. PostgreSQL 容器启动慢应用先于数据库启动。1. 检查两个密码是否完全相同。2. 使用docker-compose logs postgres查看数据库日志确认启动完成后再重启应用容器 (docker-compose restart app)。访问localhost:3000超时或报错1. Next.js 应用编译失败或未启动。2. 端口被占用。1. 运行make logs-app查看应用日志常见于 TypeScript 编译错误或依赖缺失。2. 运行docker-compose ps确认所有容器状态均为Up。3. 检查本地3000端口是否被其他程序占用。智能体不响应或提示“无可用工具”1. OpenRouter API 密钥无效或余额不足。2. 智能体会话队列 Worker 未运行。3. 数据库种子数据未正确加载。1. 在 OpenRouter 仪表板检查密钥状态和用量。2. 运行make logs查看 unified-worker 容器的日志确认它在正常轮询队列。3. 检查ops_agent_registry表是否有6条智能体记录。web_search工具返回错误1. Brave Search API 密钥未配置或无效。2. 回退的 DuckDuckGo API 暂时不可用。1. 检查.env.local中BRAVE_API_KEY设置或在 OpenRouter 日志中查看具体错误信息。2. 这是一个外部依赖偶尔失败是正常的系统应能降级或跳过。Sanctum 聊天室无法连接Sanctum WebSocket 服务未启动。1. 确保docker-compose.yml中包含了sanctum服务。2. 运行make logs查看 sanctum 容器日志。3. 浏览器控制台查看 WebSocket 连接错误。心跳API返回401未授权CRON_SECRET环境变量未设置或与请求头中的Bearer Token不匹配。1. 确认.env.local中的CRON_SECRET已设置。2. 手动触发心跳时确保 curl 命令中的$CRON_SECRET变量已正确展开或直接替换为字符串。日志是排错的最佳朋友。SubCult 采用了结构化的日志系统。在开发模式 (NODE_ENVdevelopment) 下日志是带颜色的易读格式。在生产模式 (NODE_ENVproduction) 下日志是JSON格式方便接入 ELK、Loki 等日志聚合系统。每个请求都会有一个唯一的x-request-id贯穿整个调用链让你可以轻松追踪一个用户请求或后台任务的所有相关日志。5.3 扩展与定制化思考SubCult 作为一个开源项目提供了极大的定制空间。自定义智能体你完全可以基于现有模板在src/lib/agents.ts和workspace/agents/目录下创建第七个、第八个智能体。定义它的名字、人格、声音、工具权限和失败模式然后将其注册到系统中。你可以创建一个专注于代码审查的“审查员”或一个专注于市场分析的“观察者”。开发新工具工具系统是模块化的。在src/lib/tools/tools/目录下每个工具都是一个独立的函数。如果你想增加一个“发送邮件”或“调用内部API”的工具只需遵循相同的模式实现一个新的工具函数并在工具注册表中将其分配给特定的智能体即可。记得在工具箱容器 (docker/toolbox/) 中安装任何必要的运行时依赖。调整工作流与策略系统的行为很大程度上由数据库中的策略 (ops_policy) 和触发规则 (ops_trigger_rules) 控制。你可以修改每日提案配额、调整关系亲密度计算公式、或者定义全新的触发条件例如“当项目成本超过预算50%时自动发起一场有Primus参加的紧急会议”。集成外部系统通过调用POST /api/ops/proposalsAPI你可以将外部系统如GitHub Issues、Trello卡片、客服工单的事件导入到 SubCult让这个AI团队来处理。同样通过监听系统的事件流或查询任务状态你可以将 SubCult 的产出如生成的文档、决策建议推送到其他系统。这个项目的价值不仅在于其开箱即用的多智能体聊天功能更在于它提供了一个完整的、可自举的“智能体操作系统”范本。你可以把它当作一个实验平台探索智能体协作、自主工作流、人机混合决策等前沿课题而所有代码和数据都在你自己的掌控之中。