夜雨聆风学习资料网

ARTICLE · 1299870

【AX 实战 06】官方文档说 AX 有四种 kind,我数了三遍只有三个

【AX 实战 06】官方文档说 AX 有四种 kind,我数了三遍只有三个

副标题:三个 kind 加一个作用域:Task / Workspace / Model / Atespace


上期回顾:第 5 章我们跑通了第一个 Task 的完整闭环——apply 完停在 Suspended(P0-1)、ax resume 才真正拉起 worker、ssh 要过 Running + debug: true 两道门槛。你可能已经写过好几份 YAML 了。这一章退后一步,把 YAML 里的「层」看清:docs/manifests.md 开头说 AX 有「四种 kind」,我照着数了三遍,只有三个。

本篇要点(5 行)

  1. 「四种 kind」是文档笔误:可 apply 的 kind 只有 Task / Workspace / Model 三个,第四个「atespace」是每个资源的 metadata 字段(F2)。
  2. Task是最小隔离单元:创建后不可变,一个 Task 可以是整份活,也可以是一棵任务树的根。
  3. 状态分两层读:phase 是一行摘要,Ready / WorkspaceReady 两个 condition 才是细节;suspend 时 Ready=False、reason 就
    是 Suspended,而 WorkspaceReady 翻上去就不再翻回来。
  4. Workspace声明一次、多处绑定、只准备一次(marker 机制);Model把凭证留在 Kubernetes Secret 里,配置一次处处引用。
  5. Atespace是作用域,系统自动创建(EnsureAtespace),你永远不需要 apply 它——顺带说清 Sandbox归谁管(F3)。

和多数 AX 文章不同:这一章正面纠正官方文档的笔误,并给出四条源码证据;同时把 phase/condition 的翻转时机讲到源码级,让你看 describe 的 Conditions 表时知道每一行在说什么。

目录锚点:① 纠错:四种 kind → ② Task → ③ phase 与 condition → ④ Workspace → ⑤ Model → ⑥ Atespace → ⑦ 一 Task 一 Workspace + 隔离归谁管 → ⑧ 看 YAML 判层次 → ⑨ 失败复盘 → ⑩ 一页速查

前置与难度:前置第 5 章(能 apply/resume/ssh 一个 Task)| ★2 | 约 15 分钟 | 不需要额外环境,对照源码读即可


金句:atespace 不是 kind,是户口。

一、先纠一个错:「四种 kind」是笔误(F2)

打开 docs/manifests.md,第一句话就是:

All four kinds can live in one multi-document YAML file.

「四种 kind 可以写进同一份多文档 YAML」。听起来很顺。但你把 examples/task.yaml 打开数一数:kind: Task、kind: Workspace、kind: Model——三个。

是例子少写了一种吗?不是。是文档笔误。四条源码证据:

事实
出处
proto 只有三个资源消息
ax.proto:61/130/189
只有三个 Kind 常量
types.go:34-36
CLI 报错自证只有三种
main.go:355
atespace 是 metadata 字段
ax.proto:53

一条条看。pkg/apis/v1alpha1/ax.proto 里定义的资源消息恰好三个:Task(61 行)、Workspace(130 行)、Model(189 行),其余全是 Request/Response。pkg/apis/v1alpha1/types.go 的常量区写着:

const (    KindTask      = "Task"    KindWorkspace = "Workspace"    KindModel     = "Model")

三个,没有第四个。再看 CLI 自己怎么说——cmd/ax/main.go:355,给它喂一个不存在的 kind,报错原文:

unsupported kind "Foo" (expected Task, Workspace,or Model)

括号里列了三种。CLI 是最后一道关,它不接受第四种。

那第四个「kind」指什么?大概率指 atespace。但看 ax.proto:53:

message ObjectMeta {  string name = 1;  string atespace = 2;  google.protobuf.Timestamp create_time = 3;}

atespace 是 ObjectMeta 的第二个字段——每个资源的「户口」,跟 name 同级。它有自己的创建逻辑(见第六节),但它不占一个 kind 席位。

自验(在 AX 仓库根目录跑,输出恰好三行):

grep -n "KindTask\|KindWorkspace\|KindModel" \  pkg/apis/v1alpha1/types.go# 34: KindTask / 35: KindWorkspace / 36: KindModel# 三个,没有第四个

一句话记法:三个资源,一个作用域。 后面六个小节把这四样逐个讲透。


二、Task:最小隔离单元

2.1 定义:便宜到可以随手扔

docs/concepts.md 的原话:The smallest unit of isolated execution(最小的隔离执行单元)。它声明四样东西:镜像与命令、资源上限、环境变量、(可选的)Workspace 引用。

为什么设计得这么「小」?概念文档解释得很坦白:Agent 不是「一个进程跑到底」,它的一生是规划、委派、重试、扇出(把一个任务拆成许多并行的子任务)。AX 不去模拟这个形状,只给一个便宜到可以随手创建、随手挂起、随手扔掉的原语,让 Agent 自己按需组合。

2.2 创建后不可变:两个条件的判定

这是很多人踩的第一堵墙。ax apply 一份 Task 后想改个 env 再 apply?看 internal/server/server.go:174-177 的精确判定:

if existing.Status.Phase != PhasePending ||    !proto.Equal(existing.Spec, task.Spec) {    // reject: already exists and is immutable}

翻译成人话:已存在的 Task,只要「phase 不是 Pending」或「spec 有改动」任一成立,当场拒,报错原文:

task "task123" already existsand is immutable

两个推论:

  1. 想改 spec
    :只能 delete 再 apply。注意不可变的是 spec——status(phase、conditions)当然在持续变化,否则第 5 章的状态流就不成立了。
  2. 幂等重试的口子
    :Task 卡在 Pending(比如 Substrate 暂时不可用)时,同 spec 再 apply 一次会被接受,沿用原对象继续 provision。这是留给故障恢复的通道——除了这个口子,重 apply 一律被拒(哪怕 spec 一模一样)。

对比之下 Workspace 和 Model 都有 Update 接口(第四、五节),可以原地改。Task 是三者里唯一不可变的。第 7 章把这条判定展开成完整的校验两栏。

2.3 可被 Agent 扇出成树

概念文档里有一段值得抄下来:

A single task may be the whole job, or it may be the root of a large tree of tasks spawned as the agent breaks the problem down.

一个 Task 可以是整份活,也可以是一棵任务树的根。Agent 把大问题拆小,每个小问题开一个 Task——每个节点拿到同样的沙箱、同样的生命周期、同样的工具。第 5 章你跑的 task123 是单节点;真实 Agent 场景里它可能有几十个子节点。


三、phase 与 condition:状态的两层

3.1 phase:一行摘要

status.phase 一个词说清 Task 在哪:Running、Suspended、Failed、Terminating、Pending、Resuming、Suspending、Unknown。

这八个里有一条重要的分界线(types.go 注释 + server.go 的 setActorStatus):

  • AX 自己维护的
    :Pending / Failed / Terminating——AX 的权威状态,调谐失败、删除请求由 AX 写入。
  • 跟着 Actor 走的
    :Running / Resuming / Suspending / Suspended / Unknown——从 Substrate 的 Actor 状态映射而来,以 Actor 实况为准:每次读取时 setActorStatus 用 Actor 当前状态覆盖它们。

源码里有个小函数 IsAXPhase 专门做这个判断(types.go:124-130)。为什么这么分?Actor 是 Substrate 的事实,AX 只是读;把映射状态当成 AX 自己的权威数据反而会和 Substrate 打架。

3.2 condition:两行细节

phase 是摘要,细节在 conditions。只有两个 type(types.go:73-75):

Condition
什么时候为 True
WorkspaceReady
workspace 初始化完成,之后保持 True
Ready
Task 在跑,且 WorkspaceReady=True

注意 Ready 的定义把 WorkspaceReady 包含在内——所以等就绪,盯 Ready 一个就够(概念文档原话:This is the one to wait on)。

ax describe task 的输出长这样(示例输出,格式依据 cmd/ax/main.go):

Conditions:  TYPE              STATUS  REASON   MESSAGE  Ready             True    TaskRunning  WorkspaceReady    True    SetupComplete

两行全绿 = 真就绪。只有一行绿 = 还在初始化。

3.3 suspend / resume 时,Ready 怎么翻

这是本节的正题。翻转规则(docs/concepts.md:17-18 + internal/server/server.go 的 setActorStatus):

  1. suspend
    :Ready → False,reason 就是 phase 本身——Suspended。消息形如「Actor is suspended」。
  2. resume 成功
    :Ready → True,reason TaskRunning。
  3. WorkspaceReady 不翻
    。挂起再恢复,它保持 True。

第 3 条的源码注释值得看(reconciler.go:370-371):

// setNotReady marks the task's Ready condition False.// WorkspaceReady is left untouched: workspace setup is// a one-time step whose result outlives actor failures// and suspends.

workspace 只初始化一次,它的成果比 Actor 的挂起/失败活得久。所以 resume 回来时,Ready 的判定只看 Actor 是否 running——workspace 早就 ready 了。

这里有个源码级细节可以帮你看懂 describe:types.go 的注释说「Ready 因 Actor 不在跑而为 False 时,reason 用 task 的 phase 代替」。setActorStatus 的实现确实这么干——reason 直接填 phase 值。所以你看到 reason 列写着 Suspended、Resuming、Failed 时,那是 phase 在替 Ready 说话。

到这里,状态层就讲完了:phase 是摘要,conditions 是细节,reason 列还藏着 phase 的影子。下一节把「环境」这个层次铺开。


四、Workspace:声明一次,多处绑定

4.1 它准备的是「干活前的桌面」

Agent 真正干活之前,得先把桌面铺好:仓库克隆到正确的分支、允许调用的 MCP 工具接上、技能包放到位。每个任务都重复这套准备,每个 Agent 框架都重新发明一遍。Workspace 就是把这套准备声明化、只做一次。

WorkspaceSpec 四类内容(ax.proto:137-142):

字段
解决什么
git
代码从哪来(克隆到子目录)
files
规则写在哪(比如 AGENTS.md)
mcp
 / skills
工具怎么接、技能从哪加载

细节(git 的 depth、MCP 的 registries vs servers、skills 的落点)第 8 章深潜。本章只建立定位:Workspace 是环境,Task 是干活的人。环境铺一次,人可以来很多个。

4.2 只准备一次:marker 机制

「只准备一次」怎么落地?看 internal/workspace/setup.go。Runner 在沙箱里第一次启动时执行 maiden run(少女航程):克隆仓库、挂载文件、装 MCP 和 skills。成功后写一个 marker 文件:

InitializedMarkerFilename = "initialized"

下次启动先查 marker(setup.go:85-87):

workspace already initialized;skipping maiden run setup

有 marker 就跳过整段准备。这就是 suspend → resume 之后你的 /workspace 还在、不用重新 clone 的原因——快照连 marker 一起带回来了。

反过来,准备失败会怎样?看 setup.go:102 的日志:

maiden run workspace setup completedwith errors; marker omitted to allow retry

失败不写 marker,下次启动从头再来。这是 F11 的例外(文档说「只准备一次」,实际失败会重试),第 8 章细讲重试次数与间隔。


五、Model:配置一次,处处引用

5.1 四个字段说清一个 Model

ModelSpec(ax.proto:196-204)四个字段:

字段
作用
provider
谁家的模型(google / anthropic)
model
模型 ID(如 gemini-3.8-flash)
secretKey
引用 Kubernetes Secret 里的凭证
parameters
生成参数,原样透传给 provider

关键设计在 secretKey——它是引用,不是明文。指向一个 K8s Secret 的 name + key:

spec:  provider: google  model: gemini-3.8-flash  secretKey:    name: gemini-api-secret    key: GEMINI_API_KEY

API key 躺在 Secret 里,Task 的 YAML、env、describe 输出都碰不到它。换 key 轮换时改 Secret 或重新 apply 一条 Model,不用翻所有 Task。为什么这样设计?第 9 章从 F16(config 明文会被 describe 打印)讲起,这里先记住结论:密钥走引用,永远别进 Task。

一个预告(F14):parameters 的键名以 provider 为准。proto 注释举例 maxOutputTokens(Gemini 风格),而 docs/manifests.md 的示例写 maxTokens(Anthropic 风格)——AX 不校验键名,原样透传。照抄错键名不会报错,只会静默失效。第 9 章细辨。

5.2 一句话定位

Model 是平台配置,不只是任务配置——AX 自身组件也读 Model 资源。所以它值得单独管理,而不是塞进某个 Task 的环境变量里。第 9 章从密钥轮换的完整流程讲起。


六、Atespace:不是 kind 的那一个

6.1 它是作用域,自动创建

回到第一节的结论:atespace 是 ObjectMeta 的字段。概念文档第一句:Every AX resource lives in an atespace(每个 AX 资源都住在某个 atespace 里)。默认值 default。

它对应到 Substrate 就是 namespace 一样的隔离边界。你在 YAML 里写:

metadata:  name: task123  atespace: team-a

或者用旗标 ax apply -a team-a -f m.yaml。两者冲突会被当场拒绝(main.go 的 setApplyAtespace):

metadata.atespace "default" does notmatch --atespace "team-a"

6.2 你永远不需要 apply 它

重点来了。reconciler.go:146,每个 Task 调谐时第一件事就是:

if err := r.client.EnsureAtespace(    ctx, atespace); err != nil {

EnsureAtespace——存在就用,不存在就建。用户没有 kind: Atespace 可写,apply 一个进去只会收到第一节那条 unsupported kind 报错。作用域随用随建,这是它跟三个 kind 的行为差异。

命名规则跟着 Substrate 资源名走(docs/manifests.md):RFC 1123 小写标签,≤63 字符,字母数字开头结尾。metadata.name 和 metadata.atespace 都受这条约束,违反的 apply 当场拒,不让它烂在后面的 Actor 创建里。

6.3 多 atespace 什么时候用

一个团队一个作用域:team-a / team-b 各自隔离资源名,互不踩。第 22 章讲多租户时展开。单人单集群用默认的 default 就好,不必发明复杂度。

到这里,四样东西各归其位:三个 kind 管资源,一个 atespace 管户口。剩下的问题只有一个——隔离到底归谁管?


七、为什么一个 Task 只绑一个 Workspace + 隔离归谁管

7.1 一个 Task = 一个工作目录

TaskSpec 里的 workspace 引用是单数的(ax.proto:106-109):

message WorkspaceRef {  string name = 1;  string path = 2;}

name + path,没有列表。为什么?因为 workspace 挂载的路径就是命令的工作目录(默认 /workspace,binding 可改)。一个进程只有一个 cwd。要两套环境?开第二个 Task——可以共享同一个 Workspace,也可以各挂各的,这正好回到 2.3 的扇出模型。

Workspace 的「多处绑定」指的是一份 Workspace 被多个 Task 引用(name: default-workspace 写在每个 Task 的 spec 里),跟「一个 Task 绑多个 Workspace」是两回事。

7.2 F3:Sandbox 不归 AX 管

既然讲层次,顺手拆掉一个高频混淆。AX 的路线图(docs/roadmap.md:12)里有 Sandbox / SandboxConfig,但它标注的是「待稳定的 spec」——AX proto 里没有任何 Sandbox 类型,CLI 也不接受。

同名的 SandboxConfig 在 Substrate 那边是真实 CRD,e2e 里正在用(gvisor-default)。同名不同层:

层
管什么
AX
每个 Task 一个 Actor
Substrate
Actor 跑在哪个 sandboxClass

写作与问答口径:AX 没有 Sandbox 原语;隔离后端由 Substrate 的 SandboxConfig 决定。 不要说「AX 的 Sandbox 资源」,熟悉 Substrate 的读者会当场纠正。


八、完整案例:看到任何 YAML,说出它落哪一层

本章的产出是「一眼判层」的能力。给你一份混合 YAML 示意(结构对齐 examples/task.yaml 的三段,字段已简化,不可直接 apply),练习给每段判层:

kind: Taskmetadata: {name: task123, atespace: default}# → 层:kind #1。最小隔离单元,建后不可变---kind: Workspacemetadata: {name: ws1, atespace: default}# → 层:kind #2。环境,可 Update,只准备一次---kind: Modelmetadata: {name: m1, atespace: default}# → 层:kind #3。凭证引用,可 Update

三个 --- 分隔的文档,三个 kind。它们共同的 metadata.atespace: default 是户口——作用域,不是第四段文档。

三步判层法(拿到任何 AX YAML 都适用):

  1. 看 kind:——只可能是 Task / Workspace / Model 之一;见到别的当场知道是笔误或错平台。
  2. 看 metadata.atespace——它决定资源落在哪个作用域;不写则默认 default,旗标冲突会被拒。
  3. 看 spec 归属——镜像/命令/资源归 Task,git/files/mcp/skills 归 Workspace,provider/secretKey 归 Model。放错位置 apply 会被拒不解释(下一章讲校验)。

九、失败复盘:三个真会踩的坑

坑一:apply 一个 kind: Atespace

现象:想显式建作用域,写了第四段文档。原始报错:

unsupported kind "Atespace"(expected Task, Workspace, or Model)

原因:atespace 自动创建(EnsureAtespace),根本没有这个 kind。解法:删掉那段文档,用 metadata.atespace 或 -a 指定作用域。

坑二:想改 Task 的 image 再 apply

现象:改完 YAML 再 apply。原始报错:

task "task123" already existsand is immutable

原因:Task 的 spec 不可变(server.go:176)。解法:ax delete task task123 后重新 apply。想原地改的资源是 Workspace / Model(它们有 Update 接口)。

坑三:在 AX 里找 Sandbox 资源

现象:按 docs/roadmap.md 的条目 kubectl get sandboxconfig -n ax-system,空或报错。原因:SandboxConfig 是 Substrate 的 CRD,不在 AX 这层。解法:装好 Substrate 后查它的 namespace(e2e 里能看到 gvisor-default);AX 侧只需知道隔离后端不归你配。


十、反例 / 正例

反例(照文档数 kind):「四种 kind 都能写进一份 YAML」→ 找第四种找不到 → 以为自己漏了什么 → 翻遍文档浪费半小时。

正例(本章建立的判层法):数 kind 看 types.go 三个常量 → CLI 报错文本自证 → atespace 归 metadata → Sandbox 归 Substrate。每层归属有源码出处,不靠猜。


十一、自测 3 题

  1. docs/manifests.md
     说「四种 kind」,实际有几个?第四个「kind」的真实身份是什么?(提示:F2 + ax.proto:53)
  2. ax suspend
     之后,Ready 和 WorkspaceReady 各自怎么变?reason 列会写什么?(提示:phase 代替 reason)
  3. 为什么一个 Task 只能绑一个 Workspace?想给一个任务两套环境该怎么办?(提示:cwd + 扇出)

十二、一页速查

问题
答案
到底几个 kind?
三个:Task / Workspace / Model(F2)
atespace 是什么?
作用域(户口),自动创建
Task 能改 spec 吗?
不能,delete 后重来
重 apply 会怎样?
Pending+同 spec 可重试,其余拒
等就绪看哪个?
Ready
(已含 WorkspaceReady)
suspend 后 Ready?
False,reason=Suspended
Workspace 重复准备吗?
不会,marker 拦住;失败会重试
Model 里放明文 key?
永远不,走 secretKey 引用
Sandbox 谁管?
Substrate 的 SandboxConfig(F3)

一句话小结

三个资源,一个作用域,看 YAML 先数 kind 再看户口。


十三、下期预告

第 7 章|Manifest 校验:哪些会被 apply 拒绝,哪些会变成 Failed

层次看清了,下一章进校验。同样是「提交被拒」,有的当场拒(命名、config 超限),有的 apply 成功却慢慢变成 Failed(tag 镜像、Substrate 拒绝的 limits)——两栏分清,一次 apply 成功不靠试错。


金句:atespace 不是 kind,是户口。


互动

这一章讲的是「四个层次各自归位」。

先做一个动作:打开 examples/task.yaml,给三段文档各标一行注释——它的 kind 是什么、住在哪个 atespace、spec 里放的是哪类东西。30 秒标完,判层能力就上身了。

然后说最让你意外的一点:是「官方文档真的写错了」(F2)、是「Task 居然建后不可变」,还是「suspend 后 WorkspaceReady 不翻」?评论区聊聊,我挑几个在第 7 章讲校验时专门回应。

置顶补充:读源码有个习惯——先找常量,再找报错文本。常量定义边界(三个 Kind),报错文本是最后的自证。这两个位置比叙述性文档可靠。


事实锚点:AX 4b7bf6f / Substrate v0.6.0适用边界:基于上述锚点撰写;AX 为 pre-1.0,行为可能漂移,以你实际部署的版本为准。


附:标题候选库(留档,供 §6.4 多平台改编复用)

★ 公众号推荐标题(系列统一定稿):【AX 实战 06】官方文档说 AX 有四种 kind,我数了三遍只有三个 副标题(技术定位):三个 kind 加一个作用域:Task / Workspace / Model / Atespace

类型
标题
钩子型
 ✅本篇采用
官方文档说 AX 有四种 kind,我数了三遍只有三个
利益型
看懂三个 kind 一个作用域:AX 的 YAML 从此不用猜
身份代入型
写 AX YAML 时总怕放错层?这篇把四样东西一次讲清

副标题(技术定位):三个 kind 加一个作用域:Task / Workspace / Model / Atespace


本文基于 AX 公开信息撰写,事实锚点 AX 4b7bf6f / Substrate v0.6.0(pre-1.0,可能漂移);代码行为以你部署版本为准,争议处以官方文档为最终依据。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料