Aura 0.1.0:可以钉住的第一个版本,以及代码为什么要当纸带
Aura 0.1.0 在
2026-11-07 打了标签。这是第一条可以钉住的发布通道。默认安装仍然是
nightly,每晚跟着 main
换。要这一版,把版本指到 v0.1.0。
接口还没有冻结。这一版保证的是:在它支持的系统上,装得上,自带的例子跑得起来。
口号是:让代码第一次拥有思考的能力。这里的思考不是意识。它是一件很小的事。程序读到自己正在用的规则,试一条新的,合适就留下,不合适就撤回。纸带还是那一条。
这一版钉住什么
0.1.0 是安装通道,不是另一门语言。
1 | curl -fsSL https://raw.githubusercontent.com/cybrid-systems/aura/main/install.sh | sh |
第一行装的是滚动的 nightly。第二行才是
0.1.0,对应发布文件
aura-0.1.0-linux-<arch>.tar.gz。镜像是
ghcr.io/cybrid-systems/aura:0.1.0,另外有 :0.1
和 :latest。:nightly 继续指向
main。:latest 只由带版本号的标签更新,不会写进
aura new 生成的项目。版本号是 X.Y.Z
的二进制,aura new
会把项目钉在同一个通道上。dev 和 nightly 写
:nightly。
主机是 Linux,x86_64 和 arm64。安装和冒烟覆盖 Ubuntu 24.04、Ubuntu 26.10、Debian 12、Rocky Linux 9、openEuler 24.03 LTS SP4。macOS、Windows 和其他发行版不在这一版里。打标签之前会跑一场 300 秒的并发浸泡:8 个工人,256 条 fiber。
进化策略那套独立的 starter kit 还没有钉到这个标签上。
装上之后,例子在改什么
0.1.0 的入口是:
1 | aura new evolve demo |
它先把一个定义装进正在跑的工作区:
1 | (set-code "(define params (quote (0 0)))") |
然后每一步都是同一串动作。记住这一刻,试一个新的
params,门禁认了就留下,不认就把工作区放回去。
1 | (define (evolve! steps) |
目标是靠近 (3 -2)。候选是当前这对整数各自随机走 −1、0 或
1。keep?
只问新的那对是否更近。终端上能看到留下了几步、撤回了几步,以及最后的
params。同一次运行还写 out/report.html 和
out/audit.tsv。那是给人翻的记录。真值在工作区里:params
这个定义还是原来那个,引号里的数换过,不合适的换回去了。
aura new app
是普通的模块和测试。aura new agent
是一条编排好的流水线。三份模板里,能看见「规则被改写」的是
evolve。
计算仍是纸带上的改写
计算是一件有穷的事。给一串符号,按定好的规则,机械地写出另一串符号。
图灵机把这件事拆成三件:一条可以无限延长的纸带,一个读写头,一张状态转移表。头在纸带上移动,读一格,写一格,自己进入下一个状态。λ
演算用另一套词说同一件事。(λx. x+1) 作用到 2
上,归约成
3。函数的定义、应用和归约,就是那张转移表的另一种写法。丘奇–图灵论题说,一边能算的,另一边也能算。两边一起画出「可计算」的边界。
我们手里的计算机是一台有穷的图灵机。内存是纸带上截下来的一段。程序计数器和译码器是那个状态机。LOAD
和 STORE
是读写。程序是写在纸带上的规则。它自己也是符号,所以它可以被读,也可以被另一段规则改掉。
传统路径把规则和纸带拆开了
C 把规则写成人类读得懂的文本。指针模拟纸带上的任意一格,循环模拟头的来回走,函数和递归模拟状态的转移。然后编译器把文本变成语法树,再变成中间表示,再变成机器码。CPU 执行机器码,改的是内存。
这条路很快。它不适合一个只会对符号序列做统计性改写的模型。语法空间很大:关键字、类型、模板、异常、继承,每样都有自己的形状。运行时的状态是碎的:虚表、异常帧、分配器的元数据,散在各处。代码是一份文件,跑起来的对象是另一份。模型在高维里轻轻推一下,落回这套离散的语法和运行时,可能是一个能跑的改动,也可能是一处幻觉、一个语法错误,或者一处把整棵语义弄断的改动。
模型产出的是一段新文本。外面再编译、再跑、再把报错送回去。精度靠 diff。反馈是整段重来。改完之后,没有一个稳定的编号回答:这一格还是刚才那一格吗。
Lisp 把语法收成一条,还差最后一步
Lisp 的表面几乎只有一条规则:一个运算符,后面跟着参数。S
表达式写下来就是树。代码和数据是同一种东西。生成出来的表达式可以
quote,可以
eval,可以展开成宏,中间不必再发明一种解析。
运行时也可以收得很小。一张环境,一片堆,一条栈。eval 和
apply
是运行时看得见的动作。模型在高维里的扰动,落到这么小的语法上,比较容易仍然是一棵合法的树。改动也容易只碰局部。REPL
里可以马上试。
常见的 Lisp 仍差一步。路径多半是 read
一段文本,得到一棵新的列表,再 eval。你手里上一次那个 cons
不一定还在。编辑器里的 S
表达式,和镜像里正在跑的对象,常常仍是两份。同像性说明代码可以当数据。它还没有说明:正在跑的那一格,改完以后还是那一格。
Aura 把纸带留在工作区里
Aura 用 Lisp 的表面,把纸带做成正在跑的工作区。
工作区里是一棵扁平的语法树,FlatAST。节点有编号。查询按编号走,改写按编号写。编号就是读写头停住的地址。
四件事对应纸带上的四种动作。
| 纸带 | 工作区 |
|---|---|
| 读一格 | query,按名字或按节点号找到那一格 |
| 写一格 | mutate:rebind、mutate:replace-value,改这一格的内容 |
| 走一步 | eval-current,按现在这棵树求值 |
| 先描一份再涂改 | ast:snapshot,不行就 ast:restore |
set-code
只在装入的时候用一次,把一段源码放进工作区。之后的修改不把整段文本再解析成一棵新树。果果编程课把这件事演示到了一格:七个字符串字面量在启动时钉住,翻一张卡片只对其中一个编号做
mutate:replace-value。定义号还在,字面量号还在,变的是引号里的字。0.1.0
自带的 evolve
是同一条路,粒度是一个定义:mutate:rebind 换掉
params 的身体,门禁失败就整段恢复。
这三句话是设计原则,用跑起来的动作说。
语言接住细节。模型或人给出要改的名字、候选的源码、一句理由。解析、装入、求值、记日志,是运行时的事。不需要模型自己在文本上算行号。
代码即记忆。Agent
能读、能写、能核对的那份结构,就是正在跑的程序,不是旁边另存的一份状态。工作区可以整段存下来,文件以
AURASOUL
开头。果果页面上的灯和卡片不是浏览器里的第二份真值。evolve
最后印出的 params
也不是日志里抄下来的数,是求值此刻这个定义得到的值。
闭环要能撤回。query 是感知,mutate
是行动,eval-current 是验证,ast:snapshot 和
ast:restore
让一次实验可以当作没发生过。下一步试哪一个候选,可以是
evolve
里那种随机的一小步,也可以是标准库里的搜索。信息素、控制量,是选候选的策略,不是这一刀本身。刀是:改一格,跑一下,不合适就放回去。
高维里的意图就落在这里。模型可以仍在嵌入空间里决定「想要另一版
fact」或「这一位应该是 1」。Aura
不把这个意图当成一整文件的替换。它收成一次指名的、局部的写,写完用同一次求值看结果,失败则纸带回到快照。CPU
最后执行的仍是确定性的符号变换。变的是转移规则里的一格,而且这一格的编号还在,所以下一次还能指着它说:上一笔写在这里。
和前两篇是同一件事
第一篇指出那个在外面打转的循环:生成文本,外面跑一下,再生成。精度、速度、撤回,三件事都缺。
果果把循环收成一个孩子看得见的动作。页面和程序是同一份字面量。编号不变,所以页面可以指着同一格说,这一笔是
replace-value。那一课没有 agent:spawn,没有多
Agent
管线,也没有在课上长出新节点。它要证明的是最小的闭环可以小到一个字符串。
0.1.0 把这个运行时收成可以安装的一版。evolve
用同一个闭环改一个定义,四十步,每步留下或撤回。树的形状在这个例子里不动。栈、堆、指令序列那种会增减节点的改写,要用
mutate:splice
一类操作,插完还得重新钉住新编号。开关课故意不碰。0.1.0
的例子也不碰。
和「改一个字就重读、重解析、重装整份源文件」比,这是节点上的写。和「语言自动找出所有读过这个绑定的地方,并只重做那些地方」比,这一版的例子还没有走到那一步。evolve
的工作区里,被改的就是 params 自己。适不适合,由旁边的
fitness 重算,不是编译器去重编一棵调用图。
代码即记忆
0.1.0 没有把接口冻住。它把纸带放到了你可以装上的运行时里。
装 v0.1.0,跑 aura new evolve,看
params
在工作区里换了哪几步。再打开果果那一课,看同一刀细到一个字符串节点时,画面和程序怎么一起动。
代码即记忆。计算要进化的,是写在纸带上的规则。改完以后,那一格还在。