OpenSource-Hub

ax

CLI 도구

google/ax

선언형 에이전트 오케스트레이션 엔진으로, 클러스터에서 샌드박스화된 AI 작업을 대규모로 실행할 수 있습니다.

개요

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

FAQ (5)

문제 해결
왜 `ax ssh <task> -- <cmd>`가 원격 명령에서 `-a`, `-n`, `--server`와 같은 플래그를 누락하거나 잘못 해석하나요?

이것은 글로벌 플래그 파서(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 -a -l 대신 ls -al 사용), 원격 명령어 안에서 -a, -n, --server, --context를 사용하지 마세요. -- *앞에* 배치된 플래그(예: ax ssh mytask -a team -- ls)는 계속 정상적으로 작동합니다.

원본 Issue #410
문제 해결
왜 `ax resume`이 성공을 반환한 후에도 태스크가 Suspended 상태로 남아 있나요?

이것은 커밋 ac23328 이전의 ax 버전에 존재하는 경쟁 상태(race condition)입니다. ax apply 직후(수 밀리초 이내)에 ax resume이 실행되면, 컨트롤러의 processEvent(internal/controller/worker.go 내)가 태스크를 읽어 reconcile한 뒤, 버전 검사 없이 UpdateTaskStatus를 통해 전체 상태를 다시 기록했습니다. 늦게 발생한 create 이벤트 쓰기가 ResumeTask가 설정한 Running 단계를 덮어써, CLI가 성공을 보고했음에도 태스크가 Suspended 상태에서 멈추게 되었습니다.

**수정 사항:** 커밋 ac23328 이상이 포함된 빌드로 업그레이드하세요. 컨트롤러가 재작성되면서 Redis Streams 큐가 태스크별 잠금(Redis 리스) 하의 직접 실행 방식으로 대체되었고, 이는 create/resume 작업을 직렬화하여 갱신 유실(lost-update) 구간을 제거합니다. 이제 동시성 회귀 테스트가 이 불변 조건을 고정합니다.

**이전 버전에서의 우회 방법:** 자동화 스크립트에서 ax apply와 ax resume 사이에 지연을 추가하고, 재개 후 태스크 단계를 확인하세요(예: 태스크 상태 폴링). 태스크가 여전히 Suspended이면 ax resume을 재시도하세요.

**참고:** 태스크별 잠금은 30초 TTL을 가지며 갱신되지 않는 Redis 리스입니다. CLI 데드라인(apply는 30초, suspend/resume은 15초) 덕분에 핸들러는 이 범위 안에서 동작하지만, ax delete task는 최대 2분까지 실행될 수 있으므로, 매우 느린 delete가 여전히 잠금을 보유하고 있는 동안 resume이 이론적으로는 잠금을 획득할 수 있습니다. 다만 이로 인한 상태 손실은 관찰되지 않았습니다.

원본 Issue #429
문제 해결
ax-server를 업그레이드하거나 새 Task 필드를 추가한 후 ax-task-runner가 "unknown field" 오류로 실패하는 이유는 무엇인가요?

러너는 pkg/apis/v1alpha1/types.go에서 strict protojson.UnmarshalOptions{}를 사용하여 AX_TASK_YAML 환경 변수로부터 Task를 디코딩하므로, 러너 이미지보다 최신인 Task 필드가 있으면 unknown field "egress"와 같은 파싱 오류가 발생하고 러너는 에이전트를 시작하기 전에 종료됩니다. 수정 방법 (러너 측): 기존 UnmarshalYAML과 동일한 kind별 normalizer를 실행하되 protojson.UnmarshalOptions{DiscardUnknown: true}로 디코딩하는 UnmarshalYAMLLenient 메서드를 pkg/apis/v1alpha1에 추가한 다음, cmd/ax-task-runner/main.go에서 AX_TASK_YAML/--task-file 및 AX_WORKSPACES_YAML/--workspace-file에 대해 이를 사용하세요. 기존 UnmarshalYAML은 strict하게 유지하여 ax apply로 적용된 매니페스트가 여전히 오타를 거부하도록 하세요. 또한 러너가 전혀 사용하지 않는 control-plane 전용 필드는 reconciler가 AX_TASK_YAML에서 제거하여 더 오래된 러너도 호환성을 유지하도록 해야 합니다. 참고 커밋: mastersingh24/ax@4363c253.

원본 Issue #503
문제 해결
요청이 태스크를 깨운 후 AX가 해당 태스크를 Suspended로 표시하는 이유는 무엇이며, ax ssh 연결 거부 문제는 어떻게 해결하나요?

AX는 task phase의 캐시된 사본을 자체적으로 보관하고 있었고, AX 자체가 task를 suspend하거나 resume할 때만 이를 업데이트했기 때문에, Agent Substrate router에 의해 트리거된 wake(또는 kubectl ate suspend actor와 같은 외부 suspend)는 AX에서 감지되지 않았습니다. 이로 인해 actor가 실제로는 ACTOR_STATE_RUNNING 상태임에도 ax get task가 worker 주소 없이 Suspended로 표시되었고, ax ssh는 task "..." is in phase "Suspended" (must be Running to ssh) 오류와 함께 실패했습니다.

해결 방법: PR #515가 포함된 릴리스로 업그레이드하세요. AX는 더 이상 task 상태의 자체 사본을 저장하지 않습니다. ax-server는 ax get 및 ax list를 실행할 때마다 Substrate/ATE control API에서 actor의 실시간 상태를 읽어오므로, 누가 actor를 suspend하거나 resume했는지와 관계없이 phase와 worker 주소가 항상 실제 상태를 반영합니다.

이전 버전에서의 우회 방법: ax resume task <task-name>과 같이 AX 자체를 통해 task를 resume하여 AX가 강제로 reconcile되도록 하면, 이후 ax ssh가 다시 정상적으로 작동합니다.

원본 Issue #502
문제 해결
AX SuspendTask가 Substrate 액터가 여전히 실행 중임에도 불구하고 phase Suspended와 함께 성공을 반환하는 이유는 무엇인가요?

이것은 AX의 버그입니다(main의 커밋 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를 지우며, reason TaskSuspended로 Ready=False를 설정하고, gRPC 호출은 OK를 반환합니다 — 액터가 여전히 ACTOR_STATE_RUNNING 상태임에도 불구하고.

수정 방법(메인테이너/포크용): reconciler를 패치하여 SuspendActor 오류를 삼키지 않고 전파하도록 하고, 확인된 라이프사이클 결과만 저장하도록 하십시오. 고정된(pinned) Substrate 구현은 반복된 suspension을 멱등적으로 처리하므로, 액터가 이미 suspended 상태인 경우에는 계속 성공을 반환하십시오. 다음에 대한 회귀 커버리지를 추가하십시오: 성공적인 suspension, 거부된 suspension, 이미 suspended된 액터, 그리고 Task 생성(생성은 이 reconciliation 분기를 공유함). 모호한 타임아웃 또는 취소 실패의 경우, 실패한 RPC가 액터가 여전히 실행 중임을 보장하지 않으므로, 저장하기 전에 Substrate에서 실제 액터 상태를 다시 조회하십시오.

운영상의 우회 방법: 저장된 Task phase 또는 SuspendTask 응답만 신뢰하지 마십시오 — 실행이 중지되었다고 가정하기 전에 Substrate에서 액터 상태를 직접 조회하여 suspension을 확인하십시오.

원본 Issue #441