项目简介
AX 是 Google 开源的声明式智能体编排运行时。它通过 Kubernetes 风格的清单定义任务、工作区、网关和模型,在隔离沙箱中运行不受信任的代理代码,并支持网络出站白名单。其命令行工具仿照 kubectl,提供实时监控、远程 shell 和挂起/恢复能力。
README 预览
\n \n AX\n\n\n> [!WARNING]\n> We are still actively refining our core concepts, protocols,\n> and specifications. We will likely to introduce major breaking\n> changes prior to a stable release.\n\n**Declare an agentic task with workspaces and gateway specifications. AX sandboxes it, wires up its workspace, fences its network, and helps running it at scale.**\n\nAX is a high-throughput, declarative orchestrator to run billions of autonomous agent workloads in a cluster. It runs on top of [Agent Substrate](https://github.com/agent-substrate/substrate) for sandboxed execution and is built to run billions of tasks per cluster. If you have used Kubernetes, `ax` will feel similar.\n\n```yaml\n# task.yaml\napiVersion: ax.io/v1alpha1\nkind: Workspace\nmetadata:\n name: golang\nspec:\n git:\n - repo: https://github.com/golang/go.git\n branch: "my-fix"\n---\napiVersion: ax.io/v1alpha1\nkind: Task\nmetadata:\n name: test\nspec:\n workspaces:\n - name: golang\n goal: "Ensure that Go tool chain is available and is built from source"\n debug: true # lets you `ax ssh` into the sandbox\n```\n\nThen apply it, watch it come up, and look over the agent's shoulder:\n\n```bash\nax apply -f task.yaml\nax watch task test\nax ssh test -- ls -al /workspace\n```\n\n## Why?\n\nAgents are a new kind of workload. They are neither stateless microservices nor run-to-completion batch jobs. They accumulate state, need strict isolation, call out to model APIs and tool servers, and can burn money in a loop if nobody is watching. AX gives you four small primitives that handle all of that declaratively:\n\n| You want to... | AX gives you |\n|---|---|\n| Run untrusted agent code in an isolated sandbox with CPU/memory limits | **`Task`** |\n| Pre-wire Git repos, MCP servers, and skill packages so every agent starts warm | **`Workspace`** |\n| Lock outbound traffic down to an explicit host allowlist | **`Gateway`** |\n| Configure which LLM the platform itself uses, with credentia
常见问题 (5)
故障排除为什么 `ax ssh <task> -- <cmd>` 会丢弃或错误解析远程命令中的 `-a`、`-n` 或 `--server` 等标志?
这是全局标志解析器中的一个 bug(cmd/ax/main.go,第 58-93 行):它不会在 -- 分隔符处停止解析,因此会从你的远程命令中吞掉 -a/--atespace、-n/--namespace、--server 和 --context,并将它们应用到 ax 自身。症状包括 task not found in atespace ...、namespaces "50" not found,或静默失败(例如 uname -a 会以单纯的 uname 运行)。**修复方法:** 修补解析器循环,让 -- 之后的所有内容原样传递:
if arg == "--" {
cleanArgs = append(cleanArgs, args[i:]...)
break
} else if arg == "-a" || arg == "--atespace" {
issue #373 中也链接了一个已修复的分支。**修复前的临时解决方法:** 合并短标志,使其不与 ax 的标志匹配(例如使用 ls -al 而不是 ls -a -l),并避免在远程命令中使用 -a、-n、--server 或 --context。放在 -- *之前* 的标志(例如 ax ssh mytask -a team -- ls)仍可正常工作。
故障排除为什么 ax resume 返回成功后,我的任务仍处于 Suspended 状态?
这是 ax 在 commit ac23328 之前的版本中存在的一个竞态条件。当 ax resume 紧随 ax apply 之后运行(间隔在毫秒级以内)时,控制器的 processEvent(位于 internal/controller/worker.go)会读取任务、对其进行调和,并在不做任何版本检查的情况下通过 UpdateTaskStatus 将整个状态写回。这个迟到的 create 事件写入会覆盖 ResumeTask 设置的 Running 阶段,导致任务卡在 Suspended 状态,尽管 CLI 报告了成功。
**修复:** 升级到包含 commit ac23328 或更高版本的构建。控制器重写后,用基于每任务锁(一个 Redis 租约)的直接执行取代了 Redis Streams 队列,从而将 create/resume 操作串行化,消除了丢失更新的窗口。现在有一个并发回归测试来锁定这一不变量。
**旧版本的规避方法:** 在自动化脚本中,于 ax apply 和 ax resume 之间插入一段延迟,并在恢复后验证任务阶段(例如轮询任务状态);如果任务仍处于 Suspended 状态,则重试 ax resume。
**注意:** 每任务锁是一个 TTL 为 30 秒且不续约的 Redis 租约。CLI 的截止时间(apply 为 30 秒,suspend/resume 为 15 秒)使处理程序保持在该时间范围内,但 ax delete task 最长可能运行 2 分钟,因此从理论上讲,当一个非常缓慢的 delete 仍持有锁时,resume 可能会获取到该锁——不过目前尚未观察到由此导致的状态丢失。
故障排除为什么 ax-task-runner 在升级 ax-server 或添加新的 Task 字段后会因 "unknown field" 错误而失败?
runner 使用 pkg/apis/v1alpha1/types.go 中严格的 protojson.UnmarshalOptions{} 从 AX_TASK_YAML 环境变量解码 Task,因此任何比 runner 镜像更新的 Task 字段都会导致类似 unknown field "egress" 的解析错误,runner 会在启动 agent 之前退出。修复方案(runner 侧):在 pkg/apis/v1alpha1 中添加一个 UnmarshalYAMLLenient 方法,该方法运行与现有 UnmarshalYAML 相同的按 kind 处理的规范化器,但使用 protojson.UnmarshalOptions{DiscardUnknown: true} 进行解码,然后在 cmd/ax-task-runner/main.go 中将其用于 AX_TASK_YAML/--task-file 和 AX_WORKSPACES_YAML/--workspace-file。保持现有 UnmarshalYAML 的严格模式,以便通过 ax apply 应用的清单仍然会拒绝拼写错误。此外,reconciler 应从 AX_TASK_YAML 中剥离 runner 从未使用的 control-plane 专属字段,这样即使更旧版本的 runner 也能保持兼容。参考提交:mastersingh24/ax@4363c253。
故障排除为什么 AX 在请求唤醒任务后仍将任务显示为 Suspended,以及如何解决 ax ssh 拒绝连接的问题?
AX 维护了任务阶段的自有缓存副本,并且仅在 AX 自身挂起或恢复任务时才更新它,因此由 Agent Substrate 路由器触发的唤醒(或像 kubectl ate suspend actor 这样的外部挂起操作)对它来说是不可见的。这导致 ax get task 在 actor 实际处于 ACTOR_STATE_RUNNING 状态时显示 Suspended 且没有 worker 地址,并且 ax ssh 失败,报错 task "..." is in phase "Suspended" (must be Running to ssh)。
修复方法:升级到包含 PR #515 的版本。AX 不再存储任务状态的自有副本——ax-server 在每次执行 ax get 和 ax list 时都会从 Substrate/ATE 控制 API 读取 actor 的实时状态,因此无论由谁挂起或恢复 actor,阶段和 worker 地址始终反映实际情况。
旧版本的临时解决方案:通过 AX 自身恢复任务来强制 AX 进行状态同步,例如 ax resume task <task-name>,之后 ax ssh 即可再次正常工作。
故障排除为什么 AX SuspendTask 返回成功,phase 为 Suspended,而 Substrate actor 仍在运行?
这是 AX 中的一个 bug(在 main 分支 commit ac2332829f22360ff97b0ba34d94dd0dd782f17e 上观察到)。当 Substrate 的 SuspendActor RPC 失败时——例如返回 codes.Unavailable 或 codes.PermissionDenied——Client.SuspendActor 会正确地传播该错误,但 TaskReconciler.Reconcile 的 suspend 分支(internal/controller/reconciler.go,约 L192-L204)仅记录 "could not suspend actor on Substrate" 日志,并且在设置了 suspended 状态之后仍然返回 task, nil。随后 Server.SuspendTask(internal/server/server.go,约 L256-L306)会持久化 phase: Suspended、清除已存储的 worker IP、将 Ready=False 的 reason 设置为 TaskSuspended,且 gRPC 调用返回 OK——尽管 actor 实际上仍处于 ACTOR_STATE_RUNNING 状态。
修复方法(供维护者/fork 参考):修改 reconciler,使其传播 SuspendActor 错误而不是将其吞掉,并且只持久化已确认的生命周期结果。当 actor 已经处于 suspended 状态时仍应返回成功,因为当前锁定版本的 Substrate 实现将重复 suspend 视为幂等操作。请为以下场景添加回归测试覆盖:成功 suspend、suspend 被拒绝、已处于 suspended 状态的 actor,以及 Task 创建(创建流程共用这一 reconciliation 分支)。对于结果不明确的超时或取消类失败,在持久化之前应重新从 Substrate 查询 actor 的实际状态,因为 RPC 失败并不能保证 actor 仍在运行。
运维规避方案:不要仅信赖存储的 Task phase 或 SuspendTask 的响应——在假定执行已停止之前,应通过直接向 Substrate 查询 actor 状态来确认 suspend 是否已生效。