
这是什么
TrueFoundry 开源的 agent harness:把 agent 循环整段接过来——模型调用、MCP 工具服务器、git 支撑的技能、可选沙箱、人工批准、上下文压缩与会话状态——再以聊天界面、带 TypeScript SDK 的 HTTP API,以及可嵌入的 UI SDK 三种形式交出去。仓库建于 2026-07-23,第一次提交就是把 harness 拆进一个 pnpm workspace;此后十周里,38 个账号提交了 778 次,长出 2,938 个文件、六个发布到 npm 或 PyPI 的包、一个 Helm chart,以及 6,033 个星。同一份代码有两种跑法:本地是一个进程加 SQLite、没有登录;托管是 Postgres 加 Redis,用 Docker Compose、Helm 或 Railway 起。它的中心词汇是 agent、session、turn、event 与 delta,而 TypeScript 与 Python 两个 SDK 都从同一份 OpenAPI 规格生成。
谁做的这是 TrueFoundry 这家公司的仓库,而不是某个人的项目:778 次提交来自 38 个账号,其中 776 次能关联到 GitHub 账号。单人提交最多的是 Chirag Jain(chiragjn)119 次,随后是 debajyoti-truefoundry 77 次、bhaveshpatel640 75 次、govindavashishtha 67 次、sr07asthana 59 次、thesujai 54 次;三个机器人账号另计 62 次,还有十二个账号各只有一次提交。386 条共同作者尾注里 155 条写「Cursor」,19 条署名 Claude Opus 4.8。README 留下的唯一联系方式,是两位创始人在 truefoundry.com 的邮箱。
它是怎么搭起来的
组成 · 6一个 pnpm workspace、四层,以及一份所有层都必须认的契约。packages/trueforge-core 是 harness 本身——agent 循环、能力、MCP 客户端、沙箱 provider、事件 schema、会话与轮次句柄——里面没有 HTTP,也没有界面。packages/trueforge 是服务端:处理器、路由、两棵并行的数据库树、从 YAML 载入的目录、认证,以及一个 54 KB 的配置模块。packages/trueforge-ui 是作为库存在的界面——atoms、containers、layouts、主题与可覆写的 slot——而 packages/frontend 是随服务端包一起发布的那层薄壳。packages/assistant-ui-runtime 是把 React 聊天运行时接到服务端事件流上的适配层,也是那些别人不许再声明一遍的端口类型的归属地。生成物挂在这份契约上:一份 333,161 字节的 OpenAPI 文档在 CI 里重新生成、并保持两份逐字节相同,它产出 1,010 个文件的 TypeScript SDK 与 403 个文件的 Python SDK,两边都不许人工修改。分层带来两个后果:因为存储是一个接口加三种实现,本地与托管部署的差别只在于构造哪一个;而因为沙箱、模型服务商、技能来源与网页搜索都是随包发布的 YAML 目录,出厂默认值属于配置,而不属于代码。
- packages/trueforge-core/(188 个文件,1,165 KB)
- 不带传输层的 harness:
AgentThread.ts(53,368 字节)与AgentThreadOrchestrator.ts(19,894 字节)跑循环;core/capabilities/builtins/装着子 agent、压缩、大工具响应、OpenUI、网页搜索与追问;core/llm/是一个 53,436 字节的适配器,自带五个测试文件;core/mcp/是远端与本地的工具服务器;core/sandbox/是沙箱、它的 provider,以及两个 11,943 与 29,098 字节的 Python 辅助脚本;agent-session/是会话与轮次句柄、存储接口,以及那份 128,154 字节的契约测试。 - packages/trueforge/(459 个文件,2,315 KB)
- 服务端。
src/apis/是处理器——turns.ts35,765 字节、sessions.ts22,989 字节、mcpServers.ts19,634 字节、schedules.ts19,299 字节——下面是src/routes/;src/db/为 Postgres 与 SQLite 各镜像一套存储;src/sandbox/local/在宿主机上实现了一个沙箱 provider,含一个 27,969 字节的 provider 与一个 21,197 字节的执行器;src/truefoundry/用同一个接口桥接公司自己的平台,其中一个客户端就有 26,581 字节;五份 YAML 目录提供出厂预设。 - packages/trueforge-ui/(559 个文件,2,776 KB)与 packages/frontend/(24 个文件)
- 可嵌入的界面:
src/atoms/下直接放着 58 个文件,四种布局模式(dock、drawer、sidebar、widget),一套 slot 与主题系统外加一份 26,577 字节的主题文档,还有 68,869 字节的 changelog、25,328 字节的 README,以及自己的贡献者与安全文件——这是一个「原本住在别处」的包该有的长相。旁边的 frontend 壳是一个小的 Vite 加 React 应用,带登录与登出界面,随服务端包一起发布。 - packages/assistant-ui-runtime/、packages/trueforge-sdk/ 与 python/trueforge_sdk/
- 适配层与两个生成客户端:75 个文件的 React 运行时胶水,其中
server/types.ts39,681 字节,是服务端端口的唯一定义,另有一个 42,394 字节的 hook 文件和一个 100,655 字节的测试;然后是 1,010 个文件的 TypeScript SDK 与 403 个文件的 Python SDK,两者都从.github/fern/openapi/openapi.json(333,161 字节)生成,每个资源都带 wire 测试。 - docs/ 与 benchmark/
docs/顶层十四个文件——docs.json、一份 333,161 字节的openapi.json和十二个页面——再加三个 API 页面、22 个 UI SDK 页面、五个能力页面,以及 agent 创建、认证、harness 初始化各一页;54 张截图共 21,702 KB,另有 11 个素材 1,960 KB。benchmark/是 11 个文件的 Python 测试台,最大的脚本 16,205 字节,README 里那场与 Claude Managed Agents、deepagents 的对比就是从它来的。- 根目录文件、工作流与部署
- 一份 5,822 字节的根
AGENTS.md与九个嵌套版本,九个各 11 字节的CLAUDE.md,.cursor/BUGBOT.md加三个规则文件与两份包级 bugbot 文档,十一条待发布的 changeset,九个工作流(领头的是一份 20,583 字节的发布文件与一份 12,319 字节的 CI 文件),16,775 字节的CONTRIBUTING.md与 13,607 字节的RELEASING.md,一个两阶段 Dockerfile 加一个 npm 变体,两份 compose 文件,一个 values 文件 15,704 字节、helpers 模板 33,412 字节的 Helm chart,以及一个装着 3,927 字节基础设施文件的.railway目录。
取舍,以及它替代了什么
把会话、轮次与一串事件流当作对外契约 替代 一次「提示进、回答出」的调用
概念页把层级定义成一个 agent 对多个 session、每个 session 多个 turn、每个 turn 一串 event、部分 event 带 delta,然后直接把后果画出来:轮次自动串链,因为
previous_turn_id默认是auto,所以调用方从不重发历史;一个会话同一时刻只跑一轮;每个事件都带 id、thread id 与序号,正是这一点让断开后能续上;而 delta 只活在实时流上,因为事后列出一个轮次的事件时它们已经并好了。同一份代码,本地用 SQLite、托管用 Postgres 替代 只选一个部署目标
README 的模式表把「个人使用」对应到一个进程、SQLite、没有额外基础设施,把「团队、多副本」对应到 Postgres 加 Redis;代码里则保留两棵迁移树与一个存储接口,契约测试两边都跑,于是差别只是一个构造函数而不是一次分叉。文档对代价同样直白:standalone 模式即便配了 OIDC 也会忽略它,chart 的开发默认值包含一个人尽皆知的 Postgres 密码和一个不带鉴权的 Redis,而本地模式被描述成应当留在 localhost 的东西。
SDK 全部生成,OpenAPI 文档谁都不许手改 替代 每种语言各写一份客户端
两份规格都在 CI 里重新生成,并被要求保持完全一致;TypeScript 与 Python 两棵 SDK 树在 pull request 清单里被标为生成物;fork 的 PR 被要求只改源码,合并之后由维护者重新生成 SDK。重新生成还要带一条自己的 changeset,于是一次生成同样是受版本管理的一次改动——正是这套纪律让 SDK 成为契约面,而不是契约的副本。
沙箱默认关着,按需创建 替代 在服务端进程里跑 agent 的代码
README 把这件事同时写成功能与边界:一个用来跑代码、文件与 shell 命令的隔离环境,默认关着,只在需要时创建,而密钥留在 harness 里。技能与 Code Mode 都依赖它,它是按 provider 接口写的,有三种实现和一套共用契约测试——这也正是外面那条「再加一种 provider」的请求,变成配置问题而不是重写的原因。
按标签批准,而不是拦住每一次工具调用 替代 所有可能写数据的工具都停下等人
默认策略只点了两个标签,
@write与@destructive;而那一页自己记下了失效方式:这些标签只匹配 MCP 服务器标注过的工具,很多服务器不打标签,于是一个会改数据的工具就会不被询问地跑掉,除非点名它或者把策略设成@all。持久策略按工具名存档,所以 MCP 那侧一次改名就会让它悄悄失效;那条关于暂停轮次的开放 PR 则展示了另一面——一条保持打开的流,以及为等待而持续付出的资源。
依据README.md(9,423 字符,从 GitHub API 取到全文)、AGENTS.md(5,822 字符)、docs/api/overview.mdx(13,586 字符)、docs/authentication/overview.mdx(12,546 字符)、docs/create-agent/overview.mdx(25,194 字符)、.github/fern/generators.yml、.changeset/、.github/workflows/ 下的九个工作流、packages/trueforge/src/db/ 下的两棵迁移树,以及完整的 2,938 个文件树及其体积。
制作过程
6 个阶段- 01
工作单位是会话、轮次,以及一串带类型的事件
这个项目真正卖的东西是一套词汇。agent 是一份被保存下来的定义——模型、指令、MCP 服务器与配置——而且明确不是一个在跑的进程;session 承载一段对话的上下文;turn 是其中一次请求;一轮会以 JSON 对象的形式往流上发事件,而模型输出以增量到达,客户端按 id 把它们并进那条基础事件。文档里的事件集合小到可以逐个念出来:
turn.created、mcp.initialize、model.message、tool.response、tool.approval_required与turn.done,另有thread.created与thread.done宣告子 agent 的线程;每个事件都带id、thread_id(根 agent 是main,子 agent 是生成的 id,运行级事件是null)以及一个序号,文档说它存在的意义就是断开之后还能续上。轮次自动串成链——previous_turn_id的默认值是字符串auto——于是应用只要存住会话 id,从不需要重发历史。一个会话同一时刻只跑一轮,而一轮会在三处停下:批准、追问(tool.response_required)或 MCP OAuth(mcp.auth_required),每一处都靠再发一轮继续。词汇背后的代码才是分量所在:SessionHandle.ts19,965 字节,TurnHandle.ts18,924 字节,存储接口 17,814 字节,而那份共用的存储契约测试有 128,154 字节。 - 02
十周,778 次提交,以及一条被反复重开的版本线
第一次提交的日期是 2026-07-23,内容是「Initial commit: extract agent harness into pnpm workspace.」——这几乎就是全部的出身故事:一个 harness 从更大的产品里被拆出来,单独成库。提交曲线是 7 月 57 次、8 月 412 次、9 月 309 次,共 778 次,最新一次在 2026-09-30。周围的数字是 6,033 个星、486 个 fork、17 个 watcher、99 个开着的 issue,以及一个装了 2,938 个文件、44,151 KB 的仓库。发版没那么整齐。仓库同时发布六样东西——
@truefoundry/trueforge、trueforge-core、trueforge-ui、trueforge-sdk、assistant-ui-runtime、一个 Python 的trueforge_sdk——外加 Helm chart,而它们的版本号并不一起走:9 月底服务端在 0.3.1、UI 在 0.4.1、SDK 在 0.2.1、chart 在 0.3.0,每一个背后都还拖着-rc线。2026-09-29 有一条标题为「Reset versions to 0.0.0」的 pull request,把每个包和两份 OpenAPI 文档全部改回 0.0.0;Changesets 机器人随后开出发布 PR,把它们推向 0.0.1 与 0.4.0-rc.1。可见的 tag 又是第三条线——v0.1.1到v0.1.10-rc.1,旁边还有@truefoundry/trueforge-ui@0.3.0-rc.11这样的按包 tag——而一条发布工作流的测试计划里仍然写着:要在release-v0.176.0分支上为0.176.0-rc.1重跑一遍,那是当前这些包永远到不了的版本号。 - 03
发布流水线上的三个 bug,和一大堆写给代码的成文法
这个仓库里最有用的 pull request 全都关于流水线,而不是 agent。一条发现发布工作流会写出
skip=false并在 job 之间传递,而 GitHub Actions 会丢掉值为字符串false的 job output——于是两个镜像构建和 Helm chart 发布都被静默跳过;这个标记后来改成build或skip。另有两条讲的是「前一个 job 被跳过、后面跟着被跳过」:dispatch 会跳过select-mode,push 会跳过version-release,下游那些没有状态检查的条件于是为假;现在镜像构建、Helm、npm 与 PyPI 发布都用!cancelled(),并显式声明自己依赖哪些 job。第四条讲一个悄悄长胖的生产镜像:pnpm fetch在 store 阶段填满了/pnpm/store,同时留下一整棵node_modules/.pnpm,后续阶段把它连同 esbuild、TypeScript 这些只在构建期用的包一起带进了运行时镜像;这一步于是变成pnpm fetch && rm -rf node_modules。这些修复旁边还堆着异常多的成文法。AGENTS.md是 5,822 字节的强制规则:禁止断言逃逸(as T、as unknown as T、非空!、as never);在 catch 里另抛的错误必须带上{ cause: caught };每个类型、schema 与 helper 只能有一个归属者,不许有转发垫片;服务端端口类型只存在于packages/assistant-ui-runtime/src/server/types.ts,UI 包只准转出别名;环境变量读取必须走 54,846 字节的config.ts;界面长度用 rem 不用 px;注释解释意图,且不许带 tracker 编号。每一个嵌套的AGENTS.md还必须配一个兄弟文件CLAUDE.md,里面只有@AGENTS.md这一行——好让 Cursor 与 Claude Code 读到同一套局部规则;两边各有九个,而九个CLAUDE.md全都是 11 字节。 - 04
两套数据库、一份契约,以及一个被故意留空的登录
每一个存储都写了两次。
packages/trueforge/src/db/postgres/下有 38 个迁移,从 2026-07-27 排到 2026-09-29;packages/trueforge/src/db/sqlite/下有 36 个,从 2026-07-30 排到同一周,两棵树按主题对齐——会话、agent、MCP OAuth、技能、沙箱 provider、定时任务、会话指标、沙箱环境,其中唯一一个只在 Postgres 上出现的会话元数据 GIN 索引解释了部分差异。让它们不敢乱跑的,是一组只写一次、先打在内存存储上、再分别打 Postgres 与 SQLite 的契约测试:光storeContractSuite.ts就有 128,154 字节,另有 agent、MCP server、model provider 与 OAuth token 各自的套件,服务端包里共六份 jest 配置,其中两份留给本地沙箱。这套拆分是产品最主要的部署决定,而文档把代价写明了:本地模式是一个进程加 SQLite、完全没有登录;standalone 模式即便设了 OIDC 变量也会忽略它;README 说本地模式不是生产、也不是面向公网的用法,请把它留在 localhost,并声明超出这个范围的数据丢失与未授权访问概不负责。认证文档还补了一句:Helm chart 默认无登录、用一个人尽皆知的打包 Postgres 密码、Redis 不带鉴权;然后它列出两个洞而不是藏起来——会话历史只属于创建者,所以管理员今天不是全局超管,而任何人建的 agent 在实例上所有人都看得见。 - 05
默认关着的沙箱,和相信标签的批准机制
沙箱是可选的、按需的:文档说它默认关着,只在 agent 需要时才创建,并说密钥留在 harness 里、不进沙箱。技能与 Code Mode 都依赖它,而它是按 provider 接口写的——
DaytonaProvider.ts21,556 字节,一个由 TrueFoundry 自己支撑的 provider 11,818 字节,还有一个跑在本机的 provider 27,969 字节,带一份 Lima 配置、一个回环探测和一个冒烟脚本——所以外面那条「支持 NVidia OpenShell」的请求,才能被当作 Daytona 的替代品提出来,也被当作扩展点而不是重写。技能是 git 支撑的SKILL.md包,由一个 29,098 字节的下载器挂进沙箱,Code Mode 则通过 NATS 与沙箱通信。人工检查点的文档更有意思,因为它记下了自己的洞:工具批准默认取require_approval_for_tools的["@write", "@destructive"],而那一页在提示里写明这些标签只匹配 MCP 服务器自己标注过的工具——很多服务器不打标签,于是一个会改数据的工具可以不被询问就跑掉,补救办法是点名那个工具,或者设成["@all"]。最近一条 pull request 加了按「服务器 + 工具」存档的持久批准策略,写明「Approve once」永不落盘,也承认 MCP 那侧改了工具名之后,已有策略会匹配不到任何东西。还有一条开着的 PR 会改变暂停轮次的形状:不再以某个结果收尾,而是发出paused,把连接和资源一直敞着直到 abort;如果消费方就这么走了,它就停在 paused,而不是关闭。 - 06
一个公司仓库、一条机器人密集的队列,和两个外人
这些活属于一家公司,分散在 38 个账号上。提交最多的是 Chirag Jain(
chiragjn),119 次;随后是debajyoti-truefoundry77 次、bhaveshpatel64075 次、govindavashishtha67 次、sr07asthana59 次、thesujai54 次;其中三个账号是机器人,另有十二个账号各只有一次提交。386 次提交带共同作者尾注——155 条只写「Cursor」,49 条来自发布机器人,43 条来自一个叫trueforge-dev-bot的项目机器人,19 条署名 Claude Opus 4.8——这是一段在与 agent 协作中写出来的历史的纸面痕迹。队列被仪表化得很重:最近三十个 issue 与 pull request 里有三组 Dependabot(一组一次提议 51 个版本升级,两组只为undici的一个安全补丁,其中一条被关掉,理由是这些依赖已经没法继续升级)、CodeQL 与镜像扫描工作流、每条 PR 下面准时报到的 changeset 机器人,以及嵌在正文里的 Cursor 摘要与 agent 链接。那个窗口里有两条来自公司之外的贡献,都值得读。一条报告SandboxArtifactDownload.tsx里的parseSandboxArtifacts用了正则([^)]*),于是路径/tmp/report(final).csv在拼下载 URL 之前就被截成/tmp/report(final;报告者提出改成能识别配对括号的解析器,说明更宽的正则会把相邻链接一起吞掉,并请求维护者确认范围、把这条 issue 指派给他。另一条请求把 NVidia 的 OpenShell 支持成沙箱 provider。还有一条是内部的:一个安全 PR 关掉了一条 INFOSEC 披露,做法是在浏览器 cookie 里加 state,让 MCP OAuth 的回调绑定到发起它的用户。项目还发布了自家的基准,拿 TrueForge 与 Claude Managed Agents 和 deepagents 在同样的任务、工具与模型上比较,声称精度相同而成本更低,并把复现脚本提交在benchmark/下。
相关档案
全部档案 →第 065 号
GSD Core
「Git. Ship. Done.」——一个元提示、上下文工程与规格驱动开发的框架,每个里程碑都重复同一条五步回路:讨论、计划、执行、验证、发版。重活被推给上下文全新的子 agent,主会话因此保持轻量;每一项决定都写进规划目录下的 Markdown 与 JSON,而不是留在对话里。
第 129 号
Agents Universe
一套开源智能体平台,让同一个项目里的人共用一份上下文。项目选中时智能体把全部知识读进上下文,干活过程中再把学到的东西写回同一批文件;知识是磁盘上的 Markdown,数据库只做索引,没有嵌入模型,也没有向量检索。
第 102 号
DeepSeek Harness
DeepSeek 的 agent 运行框架:模型适配器、工具注册表、会话日志、乃至 agent loop 本身,全都是插件——换掉它们靠的是改配置文件,而不是分叉源码。