为了 token 自由,我把 jeeflow 工作流引擎用 MoonBit 写了一遍

0 阅读1分钟

一、先说钱

MoonBit 官网首页那行字是"为智能体打造的语言与工具链",它下面此刻挂的 banner 就是这场赛事:个人最高奖金 7.2 万元。

比赛每个月办一期,报名和验收截止是同一天。月度项目支持是 150 元启动、350 元完成,一个项目过两道审核就是 500 块。

我报名有一半是为了这 500 块,打算拿去充 AI 编程的额度。token 自由离我还远,先自由一个月算一个月。

要拿这笔钱,得先交出一个仓库。九月我交的是 jeeflow-moon:把工作流引擎用 MoonBit 又写了一遍。两封邮件现在都到了,第一封是报名初审通过,第二封是正式验收通过。初审那封之后 150 元启动支持到了,验收这封写的是完成支持(350 元)的具体发放时间和方式还要等赛事群通知——所以现在是到手一半,另一半在路上。

两封通知:报名初审通过、正式验收通过(邮箱已打码)

真正想写的是后面这件事:为了让这个仓库够得上验收那六条,我把 MoonBit 上写引擎该走的路全走了一遍。它是 jeeflow 八种语言实现里的第七种,跟另外六种跑同一套判据。

二、验收那六条,逐条要有东西对上

官方给的验收标准就六条:以 MoonBit 为主要实现语言、仓库公开且提交记录连续可追踪、能够运行(清晰 README + 可运行示例 + 必要测试)、本期工作有效、开源合规(认可的许可证,并说明移植或参考来源)、AI 可解释。

六条没有打分细则,但每一条都得在仓库里指出一个东西来。我把它逐条对了一遍:

验收条仓库里对上的东西
MoonBit 为主GitHub 语言统计 MoonBit 居首(现读 1,305,568 字节)
仓库公开、提交连续public;提交记录连续可追踪
能够运行Quickstart 三条命令可跑,另有一条冒烟脚本
工作有效仓库 09-04 建仓,222 笔提交全落在本期;非 merge 的 221 笔每笔都带实际改动
开源合规Apache-2.0 许可证;移植来源写在 README
AI 可解释构建目标纪律与红线写进 MAINTAINING.md

六条验收逐条对号:左边官方原话,右边仓库里指得出的那件东西

最后一条我体会最深。比赛允许 AI 参与写代码、写测试、写文档,但要求参赛者能解释技术选择——这句话的意思不是"你要能背出来",而是仓库里必须留下能解释的东西。为什么 Windows 上只跑 wasm、为什么时间要注入、为什么 SPI 全异步,这三条如果我自己说不清,这个引擎就不是我写的。

顺带一条对参赛者的提醒:GitHub 按扩展名把 moon.mod 认成 AMPL,语言统计里会冒出一小条不属于你的东西(这个仓体现在还剩 1,646 字节)。不影响"以 MoonBit 为主"的判定,但要干净的话,.gitattributes 里加一行 *.mod linguist-detectable=false 就够了。

三、这门语言写出来的引擎,形状不太一样

jeeflow 的对外契约是固定的一套:一个门面方法、一份流程 JSON、一套状态码。换成 MoonBit 写,门面照样是门面:

flow(action, args) -> {code, msg, data}

40 多个 action、99999999 错误信封、五键分页、双层状态机,这些跟 Java / Go / Python / Node / PHP / Rust 那份完全同源;流程定义用的是同一批 LogicFlow JSON,仓库里 flows/ 那 16 份夹具跟其它语言跑的是同一套。

门面到仓储:一个 flow() 收所有对外形状,异步 SPI 把持久化推给宿主

真正不一样的是下面三处,都是 MoonBit 这门语言自己决定的。

3.1 SPI 只能全异步

引擎的持久化、取人、决策回调在其它语言里常见两种形态:同步 SPI,或者同步 SPI 加一层异步桥接。MoonBit 这份只有一条路——全异步。

原因是 MoonBit 没有 block_on。异步运行时不能从同步上下文里"拧"出一个结果来,所以仓储 SPI 是 async fn,引擎是 async,门面也是 async,整条链单一事件循环,没有嵌套。核心仓储 ProcessRepository 29 个方法、扩展仓储 ProcessExtRepository 13 个方法,加上取人、搜索、元数据、拦截器、事件监听、决策 handler,注册方式统一挂在一个 Ctx 上:

let ctx = @spi.Ctx::new(repo, ext_repo)
  .with_user_provider(user)
  .with_expression_evaluator(my_eval)
ctx.register_event_listener(my_listener)

拦截器、事件监听、取人与决策 handler 也都挂在这个 Ctx 上,入参一律是异步闭包——站内信这类写副作用只有这条路才可达。

这个约束一开始看着是麻烦,后来变成了好事:它把"回调里到底能不能写库"这件事在类型上钉死了。同步形状的回调写不了库,宿主就会被迫在启动期把用户表快照进内存——之后新人入职、岗位调整,引擎永远看不见。全异步就没这个选项,所有人都在一条路上。

3.2 引擎里只有一把钟

MoonBit 的标准库没有日期时间包。能直接拿到的只有一个裸的操作系统时钟口,读出来是毫秒数;格式化、时区、日历运算都没有。

所以这份引擎把时间收口成一把钟:core/model/clock.mbt 里一个进程级的钟槽,宿主可以塞一个返回字符串的闭包进去,全引擎取"现在是几点"只走 current_time_str() 这一个口。没注入的时候它回落到 UTC——自己用日历算法把秒数摆成 yyyy-MM-dd HH:mm:ss;注入了就走宿主给的那把。MoonBit 是单线程事件循环,这个全局槽不存在并发竞争,也就不需要额外的保护。

仓里两条硬约束都挂在这把钟上:到期时间的计算必须走它,不许自己抓系统时间;统计里"今天""本周"这类边界也只认它。演示程序给宿主的接线是一行环境变量——配了时区偏移就把钟换成"系统秒加偏移",没配或者解析失败就不注入,宁可显示 UTC,也不替使用者猜一个时区出来。

对测试来说这件事的价值很直接:会签的过期窗口、委托的生效区间、按天按周聚合的统计边界,全都是可注入的量。注一个固定时刻进去,输出就是确定的,快照可以按字节比。

3.3 构建目标要写纪律

MoonBit 的 native 后端要过 C 编译。moonbitlang/async 运行时的 C 源在 Windows 上只支持 MSVC,MinGW 编不过——这是依赖自己的限制,不是这个仓库的问题。

所以仓里有一条红线写在 MAINTAINING.md:本机日常开发和单测一律 --target wasm;native 的验证放在 CI 的 Linux 上,具体是三条腿——demo-deploy.yml 的构建作业两个目标都 build 也都 test,native-probe.yml 专档跑 native 二进制起 demo 的健康检查,publish.yml 把 native 构建绿列进发版自检。

这条纪律的写法本身就是"AI 可解释"那条验收条要的东西:它写清了哪条腿在跑、哪条腿没跑(Windows 上的 MSVC 没有对应通道,所以不承诺),而不是一句"全平台支持"。

四、发出去才算一个库

引擎写完只是本地的事。这份代码按四个模块发到 mooncakes:core、persist、repository-mysql、facade,一次发版四步顺序走完,前一步不绿后一步不发。

发完还有一条用户腿:scripts/pull_verify.sh 在临时目录里新建一个跟源仓库无关的工程,只从注册表装那四个发布件,然后用发布出去的 API 跑两条——正向查一页实例,要求 code=0 且分页五键齐;负向调一个不存在的动作,要求错误信封仍然是 99999999。顺带打印这一趟解析到的第三方依赖代次。这条腿不能省:发布物内部 pin 的是哪一版、出口纪律在装出来的包里还生不生效,只有真装一次才知道。同一条链上还有个坑是给接力 pin 留的:四个模块互相引用,改代次时要把模块版本和内部 pin 一起抬,本仓用一个 moon_dep_bump.py 做这件事,抬完 moon check 的警告数要和基线全等,多一条都算这次改动带进来的。

发版链:四模块顺序发布 → 注册表回拉验证 → 插件跟版 → 镜像晋升

再往外一层是消费方。jeeflow 的集成侧有十三套框架壳,MoonBit 不走壳——它以一个插件的形式装进 mldong-moon 那套快速开发框架,成为跨语言验收矩阵里的第 14 栈。今天现读的代次是 v0.1.46,插件侧的 pin 同批跟上,镜像过完通道门禁再晋升生产。

到这一步,"这门语言接进来了"才算有凭据:注册表装得到、演示站跑得起来(jeeflow-demo.mldong.com/?lang=moon,前端用同一套 jeeflow-ui,加个 ?lang=moon 参数路由到这份引擎)、验收矩阵里有它的一格。

五、比赛给的东西,其实是一份门槛定义

写到这里可以说句实话:这个黑客松对我最大的价值不是那 500 块。

它给我的是一张清单。清单上六条,逼着一个仓库补齐平时容易拖的东西——许可证文件、能照着跑通的 README、一条完整的冒烟脚本、连续可追踪的提交记录、来源说明。这些东西在"我自己本机跑通了"的标准下全是可选项,在验收标准下全是必答题。

官方 FAQ 里有一条我挺喜欢:项目不必做得很大,比起堆功能,更看重清晰边界、可靠测试、完整文档和真实可用性。旧项目也能报,甚至可以维护别人写的项目,但验收只算本期新增的实质工作——重复提交、拆分项目、只改点简单东西都不算数。所以这条路不是刷钱的路,是一个本来就要做的东西,多了一道外部检查。

十月这期报名和验收都在 10 月 31 日截止,同一天。月度支持 150 元加 350 元,季度奖池 80,000 元(一等 12,000、二等 6,000、三等 3,000),半年度一等 24,000、年度一等 36,000,三档可叠加。我这边落袋的只有启动那一半 150 元,完成支持那 350 元按邮件口径还在等群里通知——所以这段报的是规则,加上已到账的那一半,不报战果。

如果你手上正好有一个仓库,缺的就是有人拿六条来问一遍,那这个月还剩半个月。规则正文不在官网页面上——那个页面是前端渲染的,抓不到文字,完整口径在赛事仓的 src/App.tsx 里,一条 gh api 就能读全。

参考资料