Chrome is pretty efficient, as far as per page costs go! If your architecture isn't trash!
The sin is this ridiculous era of every program shipping its own runtime. I'm headed for real down votes, but this is the same lazy culture that's taken over everywhere: go and rust both ship these huge statically compiled binaries.
When you are a company, you're only running a handful of different workloads/apps on any given box, so the statically compiled price doesn't bite you. But as users, going back to dynamically linking and making the incremental cost of loading an app not also have to carry the entire world with it is such an obvious return, and one that alas seems to not be in progress at all.
The agentic age is, I hope, going to question a lot of old assumptions & needs about what we want to run. Having lots of small resident little user agencies, compiled small scripts that are attributed to a user, is something I believe will happen, but I'm less convinced we'll recognize the face of it as it happens, that the tales will be told.
Tools like agent-substrate, ax, scion, are this interesting mix between agents, serverless, and kubernetes. I find it fascinating. (For hire on this.)
agent-substrate/substrate
@_mctrinh Interesting… it sounds like a kubernetes for AI agents to me? I think google is already working on Agent Substrate. Does this align with what you had in mind?
agent-substrate/substrate
1 like · 21 views
Instead of dedicating a pod to each agent, Agent Substrate multiplexes mostly-idle workloads across shared workers with sub-second resume.
agent-substrate/substrate
Very TBD, upcoming, early. But. Works like Agent Substrate are a new very lightweight way to allow a colossal number of ultra small processes. There's a missing middle but at a high level users are launching their own little resident ultralight processes into the cloud via this. Grid compute!
1 like
Copied @webdevcody but wanted to try ax + agent substrate, pi, and deepseek on a single linode instance running the sandboxes.
Also orchestrated by Opus 5.5 OpenBot. Video edited by a team of OpenBots.
300 buildings.
google/ax
agent-substrate/substrate t.co
7 likes · 1 repost · 1,263 views
@fatih I'm closely following this agent-substrate/substrate
2 likes · 132 views
Built for gVisor and microVMs, Agent Substrate multiplexes idle agents onto a small worker pool instead of running a dedicated pod for each.
agent-substrate/substrate
Anyone running this in production yet?
Agent Executorの概要
はじめに
Googleは2026年5月、Agent Substrateと同じ日に、エージェントの実行・再開・分散デプロイのためのオープンソースのランタイム標準としてAgent Executorを発表しました。以前、Agent Substrateの概要で整理したAgent Substrateは、エージェントを少数のworkerへ多重化する実行基盤でした。Agent Executorは、その上でエージェントの実行を管理する側にあたります。
リポジトリ名とCLI名はaxで、READMEの見出しもAXです。ブログではAgent Executorと呼ばれており、ブログからのリンク先はこのリポジトリ(github.com/google/ax)です。
この記事では、Agent Executor v0.3.1 が提供する機能と、アーキテクチャ、Task・Workspace・Modelという3つのリソース、TaskがAgent SubstrateのActorになるまでの流れを整理します。あわせて、kindクラスタで実際に動かして確認した結果も載せています。
対象コンポーネントとバージョン
| コンポーネント | バージョン | リポジトリ | 本記事での役割 |
|---|---|---|---|
| Agent Executor(AX) | v0.3.1 | google/ax | 本記事全体のソースコード調査対象 |
| Agent Substrate | 672533541dbf | agent-substrate/substrate | AX v0.3.1のgo.modが固定しているコミット。kindでの検証にもこのコミットを使用 |
| kind | v0.33.0 | kubernetes-sigs/kind | 検証環境。ノードイメージはkindest/node:v1.37.0 |
:::note alert
Agent Executorはプレビューで、READMEは破壊的変更が入る見込みであることを明記しています。
> We are still actively refining our core concepts, protocols, and specifications. We will likely to introduce major breaking changes prior to a stable release.
(参考: README.md#L6-L9)
本記事の実装説明は AX v0.3.1 に固定します。Agent Substrate は、このタグの依存先コミットを確認対象にします。別記事で扱った Substrate v0.5.0 とは API が異なるため、設定例をそのまま混ぜないようにします。リポジトリ名を省略したソースコードの引用は、すべて google/ax の v0.3.1 です。
:::
TL;DR
- AX は Task を管理し、実行を Substrate の Actor に任せます。Task・Workspace・Model は独自の gRPC API で扱い、Kubernetes の CRD にはしません。
- 状態は Redis、作業キューは Redis Streams に置きます。イベント単位の分配は、Task ごとの直列化や処理成功の保証とは異なります。
- AX の Suspend は /workspace のデータを保存します。中断時のプロセスメモリやルートファイルシステムの変更は保存せず、再開には golden snapshot と durable data を使います。
- ブログの機能説明と v0.3.1 の実装は分けて読みます。応答の再送や Task の分岐など、コードで確認できない機能もあります。
- 検証には WorkerPool と保存先の設定が必要です。arm64 では runner イメージを用意し、Task の準備完了とコマンドの成否を分けて確認しました。
第1章: Agent Executorとは
この章では、ブログが説明しているAgent Executorの機能を確認し、リポジトリのv0.3.1が提供している内容と照らし合わせます。
1.1 ブログの説明
Google Cloud のブログを参考に、機能と構成を整理します。以下の公式図はブログの説明する全体像で、v0.3.1 の実装状況は1.3節で確認します。
ブログは、長時間動くエージェントのワークフローが障害に弱く、本番環境で信頼性と効率を保って管理することが難しい、という課題から説明を始めています。
> Today, we're introducing Agent Executor, Google's open-source runtime standard for agent execution, resumption, and distributed deployment.
(参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog)
Agent Executorが備える機能として、ブログは次の5つを挙げています。
| 機能 | ブログの説明の要約 |
|---|---|
| Durable execution | イベントログとスナップショットにより、中断や障害から実行を再開する |
| Secure isolation | sandbox により、コードの副作用や悪意ある処理の影響を隔離する |
| Session consistency | single-writer により、共有セッション状態への更新を調整する |
| Connection recovery | クライアントの再接続時に、最後に受信したシーケンス以降の応答を補完する |
| Trajectory branching | checkpoint から実行を分岐し、複数の処理を試す |
(参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog)
最初の公式図は、実行を調整する Controller、履歴を扱う Event Log、実行対象の Actors という3つの要素を示しています。Actors には、エージェントだけでなくハーネス、スキル、ツール、sandbox も含まれます。
!Agent Executorの構成図。ControllerとEvent Logが双方向に接続され、Controllerから、Harnesses/Agents/Skills/Tools/Sandboxesを含むActorsへ矢印が伸びている。
(参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog, 2026-05-20)
図中の Event Log と、後述する Redis Streams の作業キューは、同じ機能として扱えません。Streams があることだけで、応答の再送や実行履歴の復元まで実装されているとは判断できません。
2つ目の公式図は、異なるエージェントや実行環境を組み合わせる構想です。Antigravity、自社で運用するエージェント、Google が管理するエージェントなどを、Agent Executor の周囲に配置しています。個々の連携が v0.3.1 に実装済みであることを示す図ではありません。
!Agent Executorを中央に置き、Antigravity(またはcustom harness)、Custom agents managed by Google、Frontier agents from Google、Self-managed agentsと双方向の矢印で接続している図。
(参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog, 2026-05-20)
Kubernetes上でのスケールについては、同日に発表されたAgent Substrateと組み合わせると説明されています。
> Agent Substrate introduces a new level of abstraction for Kubernetes that moves agents onto and off of ready compute capacity in real-time, resulting in lower latency with higher scale and efficiency.
(参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog)
Agent Substrateのリポジトリ側でも、Agent Executorはエコシステムの1つとして紹介されています。
> Agent Executor: A distributed agent runtime that demonstrates building a secure, hyper-scalable agent harness on Agent Substrate
(参考: agent-substrate/substrate README.md#L46)
1.2 リポジトリが提供するもの
google/axのREADMEは、AXを次のように説明しています。
> AX is a high-throughput, declarative orchestrator to run billions of autonomous agent workloads in a cluster. It runs on top of Agent Substrate for sandboxed execution and is built to run billions of tasks per cluster. If you have used Kubernetes, ax will feel similar.
(参考: README.md#L13)
README の「billions」はプロジェクトが掲げる規模の目標で、本記事で検証した性能値ではありません。エージェントを「ステートレスなマイクロサービスでも、完了まで走るバッチジョブでもない新しい種類のワークロード」と位置づけ、3つのリソースで宣言的に扱う、という設計です。
> Agents 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.
(参考: README.md#L47)
| やりたいこと | AXが提供するもの |
|---|---|
| 信頼できないエージェントのコードを、CPU/メモリ制限付きの隔離されたsandboxで実行する | Task |
| Gitリポジトリ、MCPサーバー、skillパッケージをあらかじめ用意して、エージェントがすぐ動ける状態で起動する | Workspace |
| プラットフォーム自身が使うLLMを、Kubernetesのsecretの認証情報とともに設定する | Model |
| エージェントを一時停止し、保存したデータを使って再開する | ax suspend / ax resume |
| 実行中のエージェントにシェルで入って中の様子を見る | ax ssh |
(参考: README.md#L49-L55の機能紹介を要約。リソース制限の実装上の注意点は6.2節)
1.3 ブログの機能とv0.3.1のコードの対応
ブログの5つの機能と、v0.3.1のソースコード・同梱ドキュメントを突き合わせると、次のようになります。「見つからなかった」と書いている箇所は、リポジトリ内をevent logやsingle-writerなどで検索した範囲での結果です。
| ブログの機能 | v0.3.1で確認できたこと |
|---|---|
| Durable execution | スナップショットによるSuspend/Resumeは、Agent SubstrateのActorTemplateのSnapshotsConfigを通じて使っている(4.4節)。イベントログに相当する実装は見つからなかった |
| Secure isolation | ActorTemplateのSandboxConfigでgVisorを指定しており、隔離はAgent Substrateのsandboxに任せている(4.4節) |
| Session consistency | シングルライターに相当する実装は見つからなかった |
| Connection recovery | 最後に受信したシーケンスから応答を補完する実装は見つからなかった。WatchTaskはTaskのステータス遷移を流すRPCで、エージェントの応答を再送するものではない(2.4節) |
| Trajectory branching | ロードマップの項目(「Stateful Task Branching」)で、実装はまだない(第7章) |
| 他のエージェントとの組み合わせ | Antigravityは、Workspaceのgoalを実行する環境準備のために使われている(5.2節)。LangChain/LangGraph、ADK、A2Aに関する記述や実装は見つからなかった |
ブログはプロジェクトの機能を説明していますが、本記事が動作を確認する対象は v0.3.1 です。表の「見つからなかった」は、このタグの公開コードで対応する実装を確認できなかったという意味です。
第2章: 全体アーキテクチャ
この章では、AXのコンポーネントと、Taskがax applyされてから実行されるまでのデータの流れを整理します。
2.1 コンポーネント
AXは4つのバイナリで構成されています。
| バイナリ | 役割 |
|---|---|
| ax | 開発者向けのCLI。マニフェストの適用、リソースの参照・watch、クラスタへのトンネルを担う |
| ax-server | ステートレスな gRPC API(ポート8080)。CLI が YAML から変換した型付きリクエストを受け、検証・保存とイベント発行を行う |
| ax-controller | 再調整を行うworker。Redisのストリームを読み、Agent Substrate上にatespaceとactorを用意して、Taskを望ましい状態に近づける。レプリカを増やしてスケールする |
| ax-task-runner | 各Taskのコンテナの中で動くエントリポイント。ワークスペースを準備し、メタデータを提供し、エージェントのコマンドを実行する |
(参考: DESIGN.md#L37-L44)
v0.3.1 の構成では、AX が Task の管理を、Agent Substrate が Actor の実行を担当します。DESIGN.md を基に、役割の境界が分かるように整理した図です。
flowchart TD
CLI["ax CLI<br/>Task / Workspace / Model を適用"]
subgraph AX["AX の制御層"]
Server["ax-server<br/>gRPC API"]
Redis[("Redis<br/>リソースの状態<br/>作業キュー / 状態変更通知")]
Controller["ax-controller<br/>Task を再調整"]
Server -->|保存・イベント発行| Redis
Redis -->|Redis Streams| Controller
end
subgraph Substrate["Agent Substrate の実行基盤"]
API["Control API<br/>Actor の配置・停止・再開"]
Actor["Worker 上の Actor<br/>ax-task-runner + エージェント"]
API -->|Worker へ割り当て| Actor
end
CLI -->|gRPC| Server
Controller -->|ActorTemplate / Actor の作成など| API
(参考: DESIGN.md#L7-L35)
2.2 etcdの外にRedisを置く理由
AXがCRDではなくRedisを使う理由は、DESIGN.mdの冒頭に書かれています。
> Storing millions of short-lived tasks as Kubernetes CRDs pushes etcd past its comfort zone (single-digit GB storage limits, write-rate bottlenecks, control plane degradation). AX keeps its state in Redis and uses Redis Streams as the work queue between the API server and a horizontally scaled pool of controllers.
(参考: DESIGN.md#L5)
これは、AX が想定する規模に対するプロジェクトの設計判断です。etcd や CRD の一律の上限を示すものではありません。数百万の短命な Task による更新負荷を避けるため、状態をRedisに置き、Redis Streamsをapiサーバーとcontrollerの間の作業キューにする、という設計です。Kubernetes の管理対象から高頻度に変わる状態を分ける点は、Agent Substrate と共通しています。AX は Task を Redis に、依存先の Substrate は Actor の状態を PostgreSQL に保存し、それぞれ異なるリソースを管理します。
RedisのキーはTask・Workspace・Modelごとに作られます。kindで実際にax applyした直後にredis-cli --scanで確認すると、次のキーがありました。
$ redis-cli --scan | sort
ax:stream:tasks
ax:task:default:simple-task
ax:tasks:atespace:default
ax:tasks:index
ax:task:<atespace>:<name>がTask本体(protojsonでシリアライズした文字列)、ax:tasks:indexとax:tasks:atespace:<atespace>が一覧用のソート済みセットで、ax:stream:tasksがイベントのStreamです。SaveTaskは、これらのキーの更新とStreamへのイベント追加を1つのトランザクションにまとめています。
pipe := s.client.TxPipeline()
pipe.Set(ctx, s.taskKey(atespace, name), data, s.opts.TTL)
pipe.ZAdd(ctx, s.taskIndexKey(), redis.Z{Score: score, Member: member})
pipe.ZAdd(ctx, s.taskAtespaceIndexKey(atespace), redis.Z{Score: score, Member: name})
pipe.XAdd(ctx, &redis.XAddArgs{
Stream: s.opts.StreamName,
Values: map[string]interface{}{
"action": "reconcile",
"atespace": atespace,
"name": name,
},
})
pipe.Publish(ctx, s.taskPubSubChannel(atespace, name), data)
(参考: internal/store/redis/store.go#L123-L176)
最後のPublishは、ax watchのためのPub/Subです。
2.3 Redis Streamsのコンシューマーグループ
ax-controllerは、Redis Streamsのコンシューマーグループ(デフォルトはax-controllers)に参加して、イベントを受け取ります。
streams, err := sub.store.client.XReadGroup(ctx, &redis.XReadGroupArgs{
Group: sub.group,
Consumer: sub.consumer,
Streams: []string{sub.store.opts.StreamName, ">"},
Count: sub.store.opts.ReadBatchSize,
Block: sub.store.opts.ReadBlock,
}).Result()
(参考: internal/store/redis/store.go#L631-L637)
通常の新規配信では、同じグループ内の1つのコンシューマーにイベントが割り当てられます。ただし、同じ Task に対する別々のイベントは、異なるコンシューマーで並行して処理される可能性があります。Task ごとのロックや single-writer を、この仕組みだけで保証するわけではありません。既存の検証では ax-controller を2レプリカにし、同じ Task に対して8回 ax apply を実行しました。以下の出力では2つのコンシューマーが登録され、どちらも pending が0であることを確認できます。これは均等配分や exactly-once 処理を示すものではありません。
$ kubectl -n ax-system scale deploy/ax-controller --replicas=2
$ redis-cli XINFO CONSUMERS ax:stream:tasks ax-controllers
name ax-controller-79c5679f9f-jj2sg-7357
pending 0
idle 279
name ax-controller-79c5679f9f-r74l6-2741
pending 0
idle 1195
(出力はinactiveを省略しています)
worker.goのRunは、processEventが失敗した場合もログに出力したうえで、必ずイベントをACKします。失敗したイベントがキューに残り続けて、後続のイベントの処理を止めることを避けるためです。
if err := w.processEvent(ctx, ev); err != nil {
slog.Error("error processing task event",
"id", ev.ID,
"atespace", ev.Atespace,
"name", ev.Name,
"action", ev.Action,
"error", err,
)
}
if err := sub.Ack(ctx, ev); err != nil {
slog.Warn("failed to acknowledge task event", "id", ev.ID, "error", err)
}
(参考: internal/controller/worker.go#L92-L103)
失敗したイベントを自動で再投入する処理はありません。すでに別のイベントがあれば処理されますが、それもなければ新しいイベントの発行が必要です。kindでも、失敗したTaskのイベントがACKされた状態(pending 0)のままFailedで止まり、ax resumeやax applyを実行して新しいイベントが発行されるまで、再調整は動きませんでした(8.6節)。
2.4 削除と状態変更の通知
ax deleteは、Taskのレコードをすぐには消しません。MarkTaskDeletingでフェーズをTerminatingにして、削除イベントを発行します。
pipe := s.client.TxPipeline()
pipe.Set(ctx, s.taskKey(atespace, name), data, s.opts.TTL)
pipe.Publish(ctx, s.taskPubSubChannel(atespace, name), data)
pipe.XAdd(ctx, &redis.XAddArgs{
Stream: s.opts.StreamName,
Values: map[string]interface{}{
"action": "delete",
"atespace": atespace,
"name": name,
},
})
(参考: internal/store/redis/store.go#L284-L317)
ax-controller は、削除イベントを受け取ると Actor を削除し、Task 用 ActorTemplate の削除を試みてから、Redis のレコードを消します。
if err := w.reconciler.ReconcileDelete(ctx, ev.Atespace, ev.Name); err != nil {
// Leave the record in Terminating so the failure is visible; re-running
// `ax delete` republishes the event and retries the cleanup.
return fmt.Errorf("cleaning up task %s/%s: %w", ev.Atespace, ev.Name, err)
}
if err := w.store.DeleteTask(ctx, ev.Atespace, ev.Name); err != nil {
(参考: internal/controller/worker.go#L107-L119)
Actor の削除が失敗すると Terminating のまま残り、ax delete を再実行すると再試行できます。一方、ActorTemplate の削除エラーは警告ログに記録するだけで、Task の削除は続行します。Task が一覧から消えたことだけでは、関連テンプレートがすべて削除されたとは限りません。
(参考: reconciler.go#L390-L405)
ax watchが使うWatchTaskも、この仕組みの上にあります。RedisのPub/Subを購読して、最初に現在のTaskをINITIALとして送り、以降は変更をMODIFIEDとして流します。更新通知で Task のフェーズが Running・Failed・Completed になるとストリームを閉じます。初回の INITIAL 送信では同じ終了判定を行わないため、すでに Running の Task を watch した場合は次の通知を待つことがあります(参考: internal/server/server.go#L201-L238)。ブログの「Connection recovery」のように、最後に受信した位置から応答を補完する仕組みではありません。
第3章: Task・Workspace・Model
この章では、AXが扱う3種類のリソースを、docs/concepts.mdとmanifests.mdをもとに整理します。いずれのリソースも、ax.io/v1alpha1のマニフェストとしてax applyで適用し、Atespace(Agent Substrate 上の論理的なグループと名前の識別範囲)ごとに管理されます。Kubernetes namespace や、認可・隔離の保証とは区別します。
3.1 Task
Taskは、隔離された実行の最小単位です。
> The smallest unit of isolated execution. A Task declares the container image and command, compute requests and limits, environment variables, and references to one or more Workspaces under spec.workspaces. Each workspace is mounted at its own path, and the first serves as the command's working directory.
(参考: docs/concepts.md#L7)
最小の Task は、イメージと環境変数だけで書けます。以下は同梱例で、8章では検証環境に合わせた runner イメージへ差し替えます。command がない場合は、runner がメタデータを提供しながら待機し、LLM を自動で呼び出すわけではありません。
apiVersion: ax.io/v1alpha1
kind: Task
metadata:
name: simple-task
atespace: default
spec:
image: "gcr.io/ax-substrate/ate-images/ax-task-runner@sha256:3a0dea6ad8b55278685db58aca6e37dc4ba04056831d45bef3aaeafdca43cac6"
env:
- name: ENVIRONMENT
value: "test"
debug: true # serve guest services so `ax ssh` works; off by default
(参考: examples/simple.yaml)
status.phaseは、Pending・Running・Suspended・Failed・Terminatingなどの1語で、詳細はconditionsに入ります。ax watchで遷移を見られます。
stateDiagram-v2
[*] --> Pending : ax apply
Pending --> Running : Resumeに成功
Pending --> Failed : 再調整に失敗
Running --> Suspended : ax suspend
Suspended --> Running : ax resume
Running --> Failed : 再調整に失敗
Suspended --> Failed : 再調整に失敗
Failed --> Running : ax resumeまたはax apply
Pending --> Terminating : ax delete
Running --> Terminating : ax delete
Suspended --> Terminating : ax delete
Failed --> Terminating : ax delete
Terminating --> [*] : ActorとActorTemplateを削除後
(参考: 初期値のPendingはstore.go#L142-L144、Failed・Suspended・Runningはreconciler.go#L193-L218、Terminatingはstore.go#L284-L317。FailedからRunningへの遷移は、kindでの確認結果をもとにした図示です)
conditionsは2種類です。
| Condition | Trueになる条件 |
|---|---|
| WorkspaceReady | すべてのworkspaceのセットアップが終わった。以降はTrueのまま |
| Ready | Actor が実行中で、controller が WorkspaceReady を True と認識している |
(参考: docs/concepts.md#L15-L18)
Running や Ready=True は、タスクのコマンドが成功したことや、Git の取得が成功したことを保証しません。5.1節・5.2節で実際の判定内容を説明します。
3.2 Workspace
Workspaceは、エージェントが作業を始められる状態にするための準備(Gitリポジトリのclone、MCPサーバー、skill)を、宣言的に1回だけ書いておくためのリソースです。
> A Workspace populates the filesystem and tool landscape. Declare it once, bind it from as many tasks as you like, and the runner materializes it inside each sandbox before the command starts:
>
> - Git repositories cloned into subdirectories of the workspace path.
> - MCP servers and registries (Model Context Protocol) that the agent can call.
> - Skill registries and the path where skills are materialized.
(参考: docs/concepts.md#L26-L30)
Taskとの紐づけにはgoalも指定できます。goalは、環境準備を自然言語で書いたもので、最初の起動時にエージェントへ渡され、ツールチェインや依存関係のインストールなどを任されます(5.2節)。
spec:
workspaces:
- name: my-service # mounted at /workspace/my-service, the command's working directory
goal: "Install dependencies and run the test suite"
- name: team-tools
path: "/workspace/tools" # explicit mount path
(参考: docs/manifests.md#L40-L47)
ただし、v0.3.1のrunnerのコードで実際に処理しているのは、Gitのclone、skillsのディレクトリの作成、goalのAntigravityへの引き渡しです(参考: internal/workspace/setup.go#L83-L124)。mcpやskills.registriesの中身は、ax describe workspaceの表示と、Taskの中の/metadata/v1alpha1/ax/workspacesが返すYAMLには含まれますが、runnerがそれをファイルに書き出す処理は見つかりませんでした。
3.3 Model
Modelは、LLMそのものではなく、名前を付けたモデルの設定です。
> A Model is not a model. It is a named model configuration: which provider to call, which model identifier to use, provider-specific generation parameters such as temperature, and a reference to the Kubernetes secret holding the API key.
(参考: docs/concepts.md#L36)
apiVersion: ax.io/v1alpha1
kind: Model
metadata:
name: default-model
atespace: default
spec:
provider: google
model: gemini-3.8-flash
secretKey:
name: gemini-api-secret
key: GEMINI_API_KEY
(参考: docs/manifests.md#L89-L101)
同梱ドキュメントは provider に google と anthropic を挙げています(参考: docs/manifests.md#L81-L123)。concepts.mdは、この設定を使う場面として「goalからworkspaceを計画するとき」を挙げていますが、v0.3.1では、Modelを読み込むPlanner(internal/workspace/planner.go)は、定義されているだけで、controllerやrunnerからは呼ばれていません。goalの環境準備に実際に使われるAPIキーは、Model リソースではなく、Atespace と同名の Kubernetes namespace にある gemini-api-secret の GEMINI_API_KEY です。取得できない場合は controller 自身の GEMINI_API_KEY 環境変数へフォールバックします。取得した値は Task のコンテナの環境変数へ渡されます(参考: reconciler.go#L352-L368)。
第4章: TaskがActorになるまで
この章では、ax-controllerが1つのTaskを再調整する流れを、reconciler.goの実装に沿って整理します。
4.1 再調整の流れ
ax-controllerのReconcileは、Taskを受け取って、Agent SubstrateのControl APIを次の順に呼びます。
sequenceDiagram
participant W as ax-controller
participant S as Agent Substrate<br/>Control API
participant A as Actor<br/>(ax-task-runner)
W->>S: EnsureAtespace
W->>S: ActorTemplateを作成(Task単位)
W->>S: EnsureActor(名前はTaskと同じ)
alt spec.suspend が true
W->>S: SuspendActor
Note over W: phase = Suspended
else
W->>S: ResumeActor
S-->>W: workerのIP
loop WorkspaceReadyになるまで(最大15秒)
W->>A: GET /readyz?check=workspace
end
Note over W: phase = Running
end
(参考: internal/controller/reconciler.go#L95-L302)
1. atespaceの作成: EnsureAtespaceで、Taskが属するatespaceをSubstrate側に用意します(参考: reconciler.go#L119-L124)。
2. Actorの名前: Actorの名前は、常にTaskの名前と同じです。atenet-routerのate-target-actorヘッダーにも、そのまま使えます。
// 2. The actor is always named after the task, so the two can be used
// interchangeably (for example in the router's ate-target-actor header).
actorName := task.Metadata.Name
task.Status.Actor = actorName
(参考: reconciler.go#L126-L130)
3. ActorTemplate: Taskごとに専用のActorTemplateを作ります(4.2節)。
4. Actor: EnsureActorでActorを作ります。すでに存在し、状態がCRASHEDなら削除して作り直します(参考: internal/substrate/client.go#L306-L366)。
5. Suspend/Resume: spec.suspendがtrueならSuspendActor、そうでなければResumeActorを呼びます。ResumeActorのレスポンスに含まれるworkerのIPが、ax get tasksのWORKER-IP列になります。
6. readyzの確認: Resumeの後、Actorの中のrunnerの/readyz?check=workspaceを500msごとに確認します。ワークスペースのセットアップは1回しか行われないため、WorkspaceReadyが一度Trueになった後は、このポーリングは行いません(参考: reconciler.go#L220-L291)。
Substrateの認証には、ax-controllerのPodに投影されたServiceAccountのトークン(audienceはapi.ate-system.svc)を使い、SubstrateのサーバーのTLS証明書は、servicedns.podcert.ate.dev/identityというsignerのClusterTrustBundleで検証します(参考: deploy/ax-controller.yaml#L69-L119)。このsignerは、Agent Substrateの概要で整理したpodcertcontrollerが提供しているものです。
4.2 Taskごとに作られるActorTemplate
ActorTemplateは、Taskごとに作られます。名前は、Taskの名前と、イメージ・環境変数のダイジェストから決まります。
// taskTemplateName derives the per-task ActorTemplate name from the task name and a
// digest of the image and container environment, so a spec change yields a new template.
func taskTemplateName(taskName, image string, env map[string]string) string {
(参考: reconciler.go#L371-L385)
環境変数には、ユーザーがspec.envに書いたものに加えて、Taskの起動設定(AX_TASK_YAML)とバインドしたWorkspaceのYAML(AX_WORKSPACES_YAML)も含まれます。ただし、statusとsuspendのフラグは除かれています。suspendを切り替えるたびに新しいゴールデンスナップショットが作られることを避けるためです。
// Only launch configuration belongs in the template; status and suspend
// changes must not create new golden snapshots.
launchTask := proto.Clone(task).(*v1alpha1.Task)
launchTask.Status = nil
launchTask.Spec.Suspend = false
if taskYAML, err := yaml.Marshal(launchTask); err == nil {
extraEnv["AX_TASK_YAML"] = string(taskYAML)
}
(参考: reconciler.go#L157-L169)
起動設定を変更すると、新しい名前の ActorTemplate が作られます。ただし、EnsureActor は既存 Actor が正常ならその Actor を返し、参照するテンプレートを更新しません。新しい ActorTemplate の作成と、既存 Actor への設定反映は別です。ax apply を Deployment のローリングアップデートと同じ動作として扱うことはできません。
(参考: client.go#L306-L365)
kindで2つのTaskを作ったときは、simple-task-tmpl-5200c4f4のような名前のテンプレートが、Taskごとに1つずつ作られました。削除時は、名前のパターンに一致するテンプレートをすべて消します(参考: reconciler.go#L409-L445)。
4.3 ActorTemplateの中身
ActorTemplateは、substrate.BuildActorTemplateが組み立てます。
Containers: []*ateapipb.Container{{
Name: "guest",
Image: image,
Command: command,
Env: envList,
Readyz: &ateapipb.ContainerReadyz{
HttpGet: &ateapipb.HTTPGetAction{
Path: "/readyz",
Port: 80,
},
},
VolumeMounts: []*ateapipb.VolumeMount{{
Name: "workspace",
MountPath: "/workspace",
}},
}},
Volumes: []*ateapipb.Volume{{
Name: "workspace",
DurableDir: &ateapipb.DurableDirVolumeSource{},
}},
(参考: internal/substrate/client.go#L212-L262)
コンテナのコマンドは常に/usr/local/bin/ax-task-runnerで、/workspaceはdurableDirのボリュームとしてマウントされます。ユーザーがspec.commandに書いたコマンドは、コンテナのエントリポイントにはならず、AX_TASK_YAML経由でrunnerが受け取って実行します(5.1節)。
4.4 スナップショットとsandboxの設定
スナップショットとsandboxの設定は、次のようにコードに固定されています。
SnapshotsConfig: &ateapipb.SnapshotsConfig{
StorageLocation: snapshotsBucket,
OnPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
OnCommit: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
OnResume: &ateapipb.OnResumeConfig{
FromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
},
},
SandboxConfig: &ateapipb.SandboxConfig{
SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR,
ConfigName: "gvisor-default",
},
(参考: internal/substrate/client.go#L263-L274)
Agent Substrateのprotoのコメントには、SNAPSHOT_CONTENT_SCOPE_DATAとRESUME_SOURCE_GOLDENが、次のように説明されています。
> Captures durable data without process memory or root filesystem changes.
(参考: agent-substrate/substrate ateapi.proto#L194-L195)
> Restores with the version's golden snapshot and the actor's own durable data.
(参考: agent-substrate/substrate ateapi.proto#L889-L890)
つまり、Suspend時に保存されるのは、プロセスのメモリやルートファイルシステムの変更を除いた、durableなデータ(/workspace)だけです。Resume時は、そのバージョン(ActorTemplate)のゴールデンスナップショットと、Actor自身のdurableなデータから復元されます。
保存するデータと、復元の土台を分けると次のようになります。
flowchart LR
subgraph Before["Suspend 前の Actor"]
Data["/workspace<br/>作業ファイル"]
Volatile["実行中のメモリ<br/>/tmp などの変更"]
end
Saved[("Actor 自身の<br/>durable data")]
Golden[("ActorTemplate の<br/>golden snapshot")]
NotSaved["中断時の状態として<br/>保存しない"]
Restored["Resume 後の Actor<br/>golden snapshot を土台に<br/>/workspace のデータを復元"]
Data -->|Suspend: DATA| Saved
Volatile -.-> NotSaved
Saved --> Restored
Golden -->|Resume: GOLDEN| Restored
AX v0.3.1 の固定設定を簡略化した図。中断直前のプロセスメモリを保存する方式とは異なります。
(参考: client.go#L249-L274、ateapi.proto#L889-L890)
Agent Substrateの概要で動かしたCounterデモは、SNAPSHOT_FIDELITY_MEMORYでプロセスのメモリごと保存していました。AXは、メモリではなく/workspaceのデータだけを保存する設定を選んでいます。これはdocs/runner.mdにも書かれています。
> The /workspace volume is what survives suspend and resume. Agent Substrate snapshots it when a task is suspended and restores it into a fresh container when the task is resumed, so the runner will see the same files but a new process tree.
(参考: docs/runner.md#L26)
スナップショットの保存先は、AX_SNAPSHOTS_BUCKET環境変数で指定します。未指定のときのデフォルト値は、コード内に固定されたバケットのURIです(参考: internal/substrate/client.go#L205-L228)。deploy/ax-controller.yamlも、同じバケットをAX_SNAPSHOTS_BUCKETに設定しているため(参考: deploy/ax-controller.yaml#L82-L83)、自分の環境にデプロイするときは、この値を差し替える必要があります(8.1節)。
第5章: Taskのコンテナの中
この章では、ActorとしてSubstrate上で動くコンテナの中で、ax-task-runnerが何をしているかを整理します。
5.1 runnerの契約
ax-task-runnerは、各Taskのコンテナで、PID 1として動くプログラムです。コントローラーは、spec.commandをコンテナのエントリポイントとして実行せず、常にrunnerを起動し、設定を環境変数で渡します。
| コントローラーが設定するもの | 値 |
|---|---|
| コンテナのコマンド | 常に/usr/local/bin/ax-task-runner |
| AX_TASK_YAML | Taskの起動設定(statusとsuspendのフラグを除く)のYAML |
| AX_WORKSPACES_YAML | バインドしたWorkspaceの、複数ドキュメントのYAML |
| /workspace | durableなディレクトリとしてマウント |
| readiness probe | ポート80のGET /readyz |
(参考: docs/runner.md#L13-L22)
runnerには、次の動作が求められます。
- ポート80でHTTPを提供し、/healthzと/readyz(ワークスペースの準備ができるまで503)、/metadata/v1alpha1/ax/taskと/metadata/v1alpha1/ax/workspacesを返す
- 各workspaceを1回だけ準備する(再起動のたびに再準備しない)
- spec.commandを、最初のworkspaceを作業ディレクトリにして子プロセスとして実行し、監視する
- コマンドが終了しても動き続ける(PID 1が終了するとメタデータサーバーも止まり、ax sshが使えなくなるため)
- SIGTERMを受けたらコマンドのプロセスグループに転送し、猶予期間の後にSIGKILLする
- spec.debugがtrueのときだけ、ゲストサービスを提供する
(参考: docs/runner.md#L28-L47)
コマンドの終了後もrunnerが動き続ける点は、コードにも現れています。
select {
case err := <-exited:
reportExit(cfg, cmd, err)
// Keep the sandbox up and inspectable until told to stop.
<-ctx.Done()
(参考: runner/runner.go#L190-L196)
docs/runner.mdには、「コマンドの終了ステータスをコントロールプレーンは現状読み取らない」とも書かれています(参考: docs/runner.md#L43)。Taskのフェーズは、コマンドが成功したかどうかとは別に決まります。
runnerは、同梱のデフォルト実装を使わずに置き換えることもできます。イメージを継承する、runnerパッケージを自分のバイナリに埋め込む、まったく別の言語で書く、の3段階があり、いずれも/usr/local/bin/ax-task-runnerに実行ファイルを置く契約を満たす必要があります(参考: docs/runner.md#L58-L134)。
5.2 ワークスペースの準備
ワークスペースの準備は、各workspaceのパスごとに、最初の1回だけ実行されます。Gitリポジトリのclone(失敗時は5回までリトライ)、skillsのディレクトリの作成、goalが指定されていればAntigravityへの引き渡しの順です。完了すると、/axの下にマーカーファイルを書き、以降の起動ではスキップします。
markerPath := filepath.Join(AXDir, MarkerName(targetPath))
if _, err := os.Stat(markerPath); err == nil {
slog.Info("workspace already initialized; skipping maiden run setup", "path", targetPath)
return res, nil
}
(参考: internal/workspace/setup.go#L83-L124)
goalの引き渡しは、antigravity_bootstrap.pyというスクリプトに対して、GEMINI_API_KEYが環境変数にある場合に、10分(AX_BOOTSTRAP_TIMEOUTで変更可)の制限時間で実行します。スクリプトかGEMINI_API_KEYがなければ、ログを出してこのステップをスキップします。
cmd := exec.CommandContext(ctx, "python3", bootstrapScriptPath,
"--goal", goal,
"--workspace", targetPath,
"--data-dir", dataDir,
)
(参考: internal/workspace/setup.go#L277-L315)
Gitのcloneに失敗しても、セットアップ全体は失敗扱いになりません。失敗はログとして/ax/git-error.logに残り、マーカーファイルだけが書かれません。コードのコメントは、これによって次の起動で再試行されることを意図しています。ただし、kindで試した限りでは、Resumeしてもcloneは再試行されませんでした(8.5節)。runner に伝わるセットアップエラーは、作業ディレクトリの作成失敗などです。この結果、Git の取得失敗が WorkspaceReady に反映されない場合があります。8.5節で、kindでこの動作を確認しています。
5.3 ax sshの仕組み
ax sshは、SSHではなく、Agent Substrateのゲストサービス(agent-substrate/env)のgRPCを使います。spec.debug: trueのときだけ、runnerが同じポート80で、HTTPとh2cを多重化してこのサービスを提供します。
> - Process service: start, inspect, stream output from, and kill processes. This is what powers ax ssh.
> - File system service: streaming file reads and writes inside the workspace.
(参考: docs/sandbox.md#L32-L33)
CLIは、kubectl port-forwardでatenet-routerへ接続し、ate-target-actorヘッダーにTaskの名前を入れて、プロセスの開始と出力のストリームを呼び出します(参考: cmd/ax/main.go#L1084-L1094、internal/guest/client.go#L45-L73)。spec.debugがfalseのTaskには、CLIの側で接続を拒否します(参考: cmd/ax/main.go#L1045-L1051)。
ゲストサービスは、任意のプロセス実行とファイルアクセスを許すため、デフォルトでは無効です。
5.4 ネットワーク
Taskには、KubernetesのServiceやIngressはありません。通常のクライアントから Task へ接続する方法として、Agent Substrate の atenet-router(ate-system名前空間のatenet-routerサービス)を使い、ate-target-actor: <atespace>/<task> ヘッダーで宛先を指定します。controller の準備完了チェックには worker IP への直接接続もあります。宛先のActorが停止中なら、先に Resume してからリクエストを転送します。これは AX の spec.suspend や Task status を更新する操作ではないため、Actor と Task の表示が一致するとは限りません(参考: docs/networking.md#L3-L5)。
第6章: 構築にあたって
この章では、AXをデプロイするために必要なものと、v0.3.1の時点で注意しておきたい点を整理します。
6.1 前提となるもの
READMEは、AXを動かす前提として、Agent Substrateがクラスタで動いていることを挙げています。
> AX schedules every task as a sandboxed actor on Agent Substrate, so Substrate must be running in your cluster before you deploy AX.
(参考: README.md#L63)
実際にkindで動かしたところ、次のものを別途用意する必要がありました。
| 追加で用意するもの | 役割 | 備考 |
|---|---|---|
| Agent Substrate | Actorの実行基盤 | AX v0.3.1のgo.modが固定するコミットを使った(Agent Substrate v0.5.0との組み合わせは確認していない) |
| WorkerPool | Actorを載せるworkerの集まり | AXのリポジトリにはWorkerPoolの記述がなく、デプロイ手順にも含まれない。無いとno free workers availableでResumeに失敗する |
| スナップショットの保存先 | Actorのスナップショットの保存先 | AX_SNAPSHOTS_BUCKETで差し替える。デフォルトはコード内に固定された値 |
| Taskのruntimeイメージ | /usr/local/bin/ax-task-runnerを含むイメージ | デフォルトのイメージはgcr.io/ax-substrate/...から取得される。make build-task-runnerはlinux/amd64向けにビルドするため(参考: Makefile#L46-L51)、arm64の環境では自分でビルドする必要があった |
| Redis | Task・Workspace・Modelの状態とイベント | deploy/redis.yamlで用意できる。レプリカ1、永続ボリュームなし |
(参考: Redisはdeploy/redis.yaml。WorkerPoolが記述に無いことは、リポジトリ内をWorkerPoolで検索した結果)
6.2 v0.3.1で注意しておきたい点
- Task のリソース設定と、実際の制限を分けて確認します。spec.resources は API に定義されていますが、このタグの BuildActorTemplate はその値を ActorTemplate のリソース設定へ渡していません。WorkerPool の Pod 制限が、そのまま Task ごとの指定値になるわけではありません。(参考: ax.proto#L71-L111、client.go#L212-L275)
- Streams の未 ACK イベントを回収する処理は確認できませんでした。新しいコンシューマーグループは $ から作られ、読み取りは > の新規イベントだけを対象にします。初回起動前のイベントや、コンシューマー停止で pending に残ったイベントを自動復旧できるとは限りません。(参考: store.go#L585-L659)
- WorkspaceReady の待機には上限があります。ポーリングが時間切れになっても phase は Running のまま、Ready は False になります。後から runner の準備が終わっても、そのことだけで再調整を起こす処理はありません。(参考: reconciler.go#L220-L302)
- ax-server に認証処理がありません。grpc.NewServer()にオプションを渡しておらず、平文のHTTP/2(h2c)でポート8080を待ち受けます(参考: internal/server/server.go#L38-L46、cmd/ax-server/main.go#L66-L74)。ax CLIはデフォルトでkubectl port-forwardを使ってax-system名前空間のax-serverサービスへ接続するため(参考: internal/tunnel/tunnel.go#L181-L200)、CLI経由のアクセスは、そのport-forwardを許可するKubernetesのRBACに依存します。クラスタ内の他のPodからax-serverへ直接到達できるかどうかは、NetworkPolicyなどクラスタ側の設定次第です。認証・ガバナンスは、ロードマップ(第7章)の項目に入っています。
- Redisは単一レプリカで、永続ボリュームを持ちません。 deploy/redis.yamlはレプリカ1のDeploymentで、ボリュームがありません。確認したRedisの設定はappendonly no、save 3600 1 300 100 60 10000(デフォルトのRDBスナップショットのみ)でした。Redis Podが再作成されると、Task・Workspace・Modelの状態が失われる可能性があります。Redisをどう構成するかは、利用者が決める前提のようです。
- 失敗したイベントの自動再投入はありません。2.3節で見た通り、失敗時も ACK を試みます。別のイベントがキューにない場合は、ax resume や ax apply などで新しいイベントを発行する必要があります。
- controller は1つのイベントを同期的に処理します。Worker.Runのループは、イベントを1つ取り出し、processEventが終わってから次を読みます。ResumeActorとWorkspaceReadyのポーリング(最大15秒)がこのループの中で行われるため、スケールはレプリカを増やすことで行います。
- Task の status.pendingApproval と status.usage は、このタグで利用する処理を確認できませんでした。 spec.policies(予算・承認の設定)は、過去のバージョンから削除済み(reserved)です(参考: pkg/apis/v1alpha1/ax.proto#L85-L87)。
第7章: ロードマップ
この章では、docs/roadmap.mdに書かれている今後の予定のうち、ブログの機能に関係するものを整理します。
ロードマップは5つの項目に分かれています(参考: docs/roadmap.md)。
| 項目 | 内容 |
|---|---|
| Stabilize Core Specs | Task・Workspace・Model・Sandbox/SandboxConfigのスキーマとライフサイクルの安定化。Taskのトークン/タイムアウトの予算や承認ポリシーも含む |
| Actor Architecture | 新しいAgent SubstrateのActor APIへの移行、ワークスペースのセットアップを別Actorに分離、最小権限のポリシー、アイドル検出と自動Suspend、Taskの分岐 |
| Agentic Environment | goalによるワークスペースの動的な環境構築、組み込みのブートストラップとエージェントハーネスのカスタマイズ |
| Networking, Identity, Governance & Observability | TaskへのSPIFFE ID、MCPとskill registryのガバナンス、OpenTelemetryによるテレメトリと軌跡の収集 |
| Documentation | Substrateとの役割分担、カスタムイメージ、カスタムrunnerのドキュメント |
ブログが挙げる機能との関係では、次の項目が対応します。
> - Idleness Detection and Automatic Suspension for Density: Continuously monitor actor activity (process execution, I/O, network traffic, and active gRPC/SSH sessions) to detect idle tasks and automatically trigger SuspendActor checkpointing, reclaiming worker CPU and memory to maximize cluster density.
> - Stateful Task Branching: Enable branching (forking) a running or suspended Task—including its checkpointed memory and workspace filesystem state—into multiple parallel tasks to explore speculative execution paths concurrently.
(参考: docs/roadmap.md#L22-L23)
ブログの「Trajectory branching」は、ロードマップの「Stateful Task Branching」にあたります。v0.3.1 はアイドルを自動検出して Suspend する実装ではありません。CLI や上位システムからの明示的な指示が必要です。「Migration to the New Actor」は、AXがAgent Substrateの新しいActor APIへ移行する予定を示しています。今回の検証では、go.modが固定するコミットのAgent Substrateを使いました。
第8章: kindクラスタで実際に動かす
この章では、既存の kind 検証記録に、再現に必要な準備と待機手順を補って整理します。arm64 環境で Task の起動・停止、保存されるデータ、失敗時の状態を確認したものです。LLM を使う goal の実行や大規模な負荷試験は対象外です。
8.1 構築
AX v0.3.1 の go.mod は Go 1.27.1 を指定しています。Docker・kubectl・ko と、この Go バージョンを使える環境を用意します。以下は arm64 用の手順です。
Agent Substrate は依存先コミットで構築します。既存の checkout を変更せずに済むよう専用ディレクトリを作ります。クラスタ作成スクリプトは同名の既存クラスタを削除するため、ax-demo が既存環境と重複しないことを確認します。
mkdir ax-kind-review
cd ax-kind-review
git clone https://github.com/agent-substrate/substrate.git substrate-pinned
cd substrate-pinned
git checkout --detach 672533541dbf
export KIND_CLUSTER_NAME=ax-demo
hack/create-kind-cluster.sh
hack/install-ate-kind.sh --deploy-ate-system
続いて、AXのRedis、ax-controller、ax-serverをkoでビルドしてデプロイします。deploy/ax-controller.yamlが設定しているAX_SNAPSHOTS_BUCKETは、kindのRustFSのバケット(ate-snapshots)へ差し替えます。
cd ..
git clone --branch v0.3.1 --depth 1 https://github.com/google/ax.git ax-v0.3.1
cd ax-v0.3.1
go install ./cmd/ax
# Go のインストール先 bin ディレクトリを PATH に追加しておく
kubectl apply -f deploy/redis.yaml
export KO_DOCKER_REPO=localhost:5001 KO_DEFAULTPLATFORMS=linux/arm64
ko resolve -f deploy/ax-controller.yaml \
| sed 's|gs://dberkov-gke-dev3/ate-env/|gs://ate-snapshots/ax/|' \
| kubectl apply -f -
ko resolve -f deploy/ax-server.yaml | kubectl apply -f -
kubectl -n ax-system rollout status deploy/ax-redis --timeout=5m
kubectl -n ax-system rollout status deploy/ax-controller --timeout=5m
kubectl -n ax-system rollout status deploy/ax-server --timeout=5m
kubectl -n ax-system get pods
以下は既存の実行結果の抜粋です。Pod 名は実行ごとに変わります。
NAME READY STATUS RESTARTS AGE
ax-controller-79c5679f9f-jj2sg 1/1 Running 0 6s
ax-redis-86df786c87-sj8z2 1/1 Running 0 30s
ax-server-7f8fc9b657-6rdnr 1/1 Running 0 6s
Taskのruntimeイメージは、デフォルトのイメージ(gcr.io/ax-substrate/...)を使わず、追加する AX の実行ファイルを /usr/local/bin/ax-task-runner だけにしたイメージを自分でビルドしました。ax-task-runnerをlinux/arm64向けにビルドし、gitを含むベースイメージにコピーして、kindのローカルレジストリにpushします。
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o ax-task-runner ./cmd/ax-task-runner
cat > Dockerfile.kind <<'EOF'
FROM alpine/git:latest
COPY ax-task-runner /usr/local/bin/ax-task-runner
ENTRYPOINT ["/usr/local/bin/ax-task-runner"]
EOF
docker build --platform linux/arm64 -f Dockerfile.kind -t localhost:5001/ax-task-runner:kind .
docker push localhost:5001/ax-task-runner:kind
RUNNER_IMAGE=$(docker image inspect localhost:5001/ax-task-runner:kind --format '{{index .RepoDigests 0}}')
RUNNER_IMAGE には完全な digest 付きの参照が入ります。続けて、検証用の simple.yaml を作ります。
cat > simple.yaml <<EOF
apiVersion: ax.io/v1alpha1
kind: Task
metadata:
name: simple-task
atespace: default
spec:
image: "${RUNNER_IMAGE}"
env:
- name: ENVIRONMENT
value: test
debug: true
EOF
:::note info
このイメージには、デフォルトのイメージに入っているPythonとAntigravity(google-antigravity)が含まれていません。そのため、goalを指定したワークスペースのブートストラップ(5.2節)は、今回の検証の対象外です。
:::
最初の Task を適用する前に、次の出力に ax-controllers グループがあることを確認します。グループは controller が起動時に作るため、未作成なら controller のログを確認して待ちます。
kubectl -n ax-system exec deploy/ax-redis -- redis-cli XINFO GROUPS ax:stream:tasks
AX の CLI は、通常は現在の kubeconfig context から ax-server へのトンネルを自動作成します。以降は kind-ax-demo を対象とし、別の接続先を指定する AX_SERVER や --server を使わない前提です。
8.2 WorkerPoolが無いとResumeに失敗する
デプロイ直後に、READMEのとおりax applyでTaskを適用すると、TaskはFailedになりました。
$ ax apply -f simple.yaml # image は上でpushした digest 指定のイメージ
task.ax.io/simple-task created
$ ax describe task simple-task
Conditions:
TYPE STATUS REASON MESSAGE
Ready False ActorResumeFailed resuming actor default/simple-task: rpc error: code = ResourceExhausted desc = no free workers available
ActorTemplateとActorの作成には成功し、ResumeActorの段階で、Actorを載せるworkerがない、というエラーになっています。Agent Substrateでは、ActorはWorkerPoolのworkerに載せられます。kind向けのインストーラが用意するのはシステムコンポーネントまでで、WorkerPoolは含まれていません。AXのリポジトリにも、WorkerPoolを作る手順はありません。以下を ax-workers.yaml として AX 側のディレクトリに保存します。nodeSelector のバージョン値は当時の記録です。再現時は kubectl get nodes -L ate.dev/substrate-version で、インストーラが付けた値と一致するか確認します。
apiVersion: v1
kind: Namespace
metadata:
name: ax-workers
---
apiVersion: ate.dev/v1alpha1
kind: WorkerPool
metadata:
name: ax-workers
namespace: ax-workers
spec:
replicas: 2
workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor
template:
nodeSelector:
ate.dev/substrate-version: "67253354"
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 250m
memory: 1Gi
(参考: demos/counter/counter.yaml.tmplをもとに、名前とreplicasを変えたもの。workerImageのko://は、ko resolveでイメージに置き換えて適用する)
ko:// の参照は、その Go パッケージを含む Substrate 側の checkout で解決します。
cd ../substrate-pinned
ko resolve -f ../ax-v0.3.1/ax-workers.yaml | kubectl apply -f -
kubectl -n ax-workers get pods -w
2つの worker Pod が Ready になったら Ctrl-C で監視を終了し、AX のディレクトリへ戻ります。
cd ../ax-v0.3.1
ax apply -f simple.yaml
ax get tasks
ax describe task simple-task
WorkerPool を作った後、ax apply をもう一度実行した既存の記録では、TaskがRunningになり、ReadyがTrueになりました。
$ ax get tasks
NAME ATESPACE PHASE ACTOR WORKER-IP AGE
simple-task default Running simple-task 10.244.0.24 2m
$ ax describe task simple-task
Conditions:
TYPE STATUS REASON MESSAGE
WorkspaceReady True SetupComplete Workspace setup completed at 10.244.0.24
Ready True TaskRunning Task is running and its workspace is ready
ax sshで中に入ると、PID 1がax-task-runnerで、spec.envの値とAX_METADATA_URLが設定されていることを確認できました。
$ ax ssh simple-task -- sh -c 'echo $ENVIRONMENT; echo $AX_METADATA_URL; ps -o pid,args | head'
test
http://127.0.0.1:80
PID COMMAND
1 /usr/local/bin/ax-task-runner
11 sh -c echo $ENVIRONMENT; echo $AX_METADATA_URL; ps -o pid,args | head
12 ps -o pid,args
13 head
8.3 Redisの状態
Redis の内容は、たとえば kubectl -n ax-system exec deploy/ax-redis -- redis-cli XLEN ax:stream:tasks で参照できます。以下の redis-cli コマンドは、その Redis コンテナ内での実行を省略して表記しています。
ax apply を3回実行した時点の既存の記録では、次の状態でした。追加で適用すると件数は変わります。ax applyのたびに、内容が変わらなくても、Streamにイベントが1件追加されます。コンシューマーグループのpendingは0です。
$ redis-cli XLEN ax:stream:tasks
3
$ redis-cli XINFO GROUPS ax:stream:tasks
name ax-controllers
consumers 1
pending 0
last-delivered-id 1792367750651-0
entries-read 3
lag 0
$ redis-cli TTL ax:task:default:simple-task
-1
最初の2回はFailedで終わっていますが、pending 0なので、失敗したイベントもACKされています(2.3節)。TTL は設定されておらず(-1)、期限による自動削除はされません。ただし、Redis 自体の再作成によるデータ消失とは別です。
8.4 Suspend/Resumeで残るもの
ax suspend / ax resume の返却は API の受付を示します。実際の状態は ax get tasks と ax describe task で確認し、Suspended への遷移後に Resume、Ready=True の確認後に接続します。ax watch は Ready=False の Running 通知でも終了する場合があるため、終了だけを準備完了と判断しません。
Suspend/Resumeで何が残るかを確認します。/workspace と /tmp にファイルを書き、バックグラウンドで sleep を起動してから、ax suspend と ax resume を実行した記録です。このときはバックグラウンドプロセスが標準出力を保持したため、ax ssh の終了時に stream terminated by RST_STREAM が出ました。再現時はプロセスの標準入出力をリダイレクトすると、出力ストリームの保持を避けられます。
$ ax ssh simple-task -- sh -c 'echo "before suspend" > /workspace/note.txt; echo "volatile" > /tmp/volatile.txt; (sleep 3000 &)'
$ ax suspend task simple-task
task.ax.io/simple-task suspended
$ ax get tasks
NAME ATESPACE PHASE ACTOR WORKER-IP AGE
simple-task default Suspended simple-task <none> 4m
$ ax resume task simple-task
task.ax.io/simple-task resumed
$ ax ssh simple-task -- sh -c 'ls /workspace; cat /workspace/note.txt; ls /tmp; ps -o pid,args'
note.txt
before suspend
PID COMMAND
1 /usr/local/bin/ax-task-runner
9 ps -o pid,args
/workspace/note.txtだけが残り、/tmp/volatile.txtとsleepプロセスは消えました。SuspendでWORKER-IPが<none>になり、Resumeではworkerが変わることもあります(この例では10.244.0.24から10.244.0.23へ変わりました)。これは、/workspaceのデータだけを保存して復元する設定(4.4節)の動作どおりです。Agent SubstrateのCounterデモのように、プロセスのメモリが引き継がれる動作ではありません。
8.5 Git の取得失敗と準備完了判定
Workspace で Git リポジトリを指定した Task も試しました。以下の image は、8.1節で取得した完全な参照へ置き換えます。
apiVersion: ax.io/v1alpha1
kind: Workspace
metadata:
name: demo-ws
atespace: default
spec:
git:
- name: origin
repo: "https://github.com/chalk/chalk.git"
branch: "main"
depth: 1
---
apiVersion: ax.io/v1alpha1
kind: Task
metadata:
name: ws-task
atespace: default
spec:
image: "<8.1節のRUNNER_IMAGEの値に置き換える>"
workspaces:
- name: demo-ws
debug: true
Taskは数秒でRunningになり、ReadyもTrueになりましたが、中を見るとcloneは失敗していました。
$ ax ssh ws-task -- sh -c 'ls /workspace/demo-ws; ls /ax; cat /ax/git-error.log'
chalk
git-error.log
error: git fetch: exit status 128
output: Reinitialized existing Git repository in /workspace/demo-ws/chalk/.git/
error: remote origin already exists.
fatal: unable to access 'https://github.com/chalk/chalk.git/': Send failure: Broken pipe
5.2節で見た通り、Gitのエラーはセットアップ全体の失敗として扱われないため、WorkspaceReady: True / SetupCompleteと表示されながら、リポジトリは空のままでした。ログの Broken pipe だけでは、失敗原因を確定できません。egress 設定やプロキシ、接続先などの切り分けは未実施です。AXのリポジトリには、egressに関する記述がロードマップの1行(「setup egress」)しかなく(参考: docs/roadmap.md#L21)、egressポリシーを設定する手順はありません。外部のGitリポジトリをcloneするには、Agent SubstrateのEgressPolicyを別途設定する必要があると思います(今回は検証していません)。
cloneが失敗したあとにResumeしても再試行されるかを、Actorの中に作るローカルのGitリポジトリで確かめました。最初は存在しないパス(/workspace/seed.git)をWorkspaceのリポジトリに指定してTaskを起動し(cloneは失敗します)、起動後にActorの中でそのパスにbareリポジトリを作ってコミットを1つ入れてから、ax suspendとax resumeを実行しました。
$ ax ssh ws2 -- sh -c 'git init -q --bare -b main /workspace/seed.git; ...' # コミットv1を入れる
$ ax suspend task ws2
# ax get tasks で Suspended を確認してから再開する
$ ax resume task ws2
$ ax ssh ws2 -- sh -c 'ls -a /workspace/demo2/src; git -C /workspace/demo2/src log --oneline'
.
..
.git
fatal: your current branch 'master' does not have any commits yet
リポジトリが存在する状態でResumeしても、/workspace/demo2/srcは空のままで、/ax/git-error.logのタイムスタンプも最初の起動のときから変わっていませんでした。手動でgit fetch origin mainを実行すると取得できたため、リポジトリ自体は有効です。この観測では、Resume による Git 取得の再試行は確認できませんでした。Agent Substrateのprotoのコメントによると、RESUME_SOURCE_GOLDENの復元は、ActorTemplateのゴールデンスナップショットとActor自身のdurableなデータから行われます(4.4節)。runnerのプロセスは、ゴールデンスナップショットから復元されている可能性があります(今回は、それ以上の確認はしていません)。
8.6 失敗とリトライ
kindでは、次の2つの失敗を確認しました。いずれも自動では再試行されず、ax resumeなどで新しいイベントを発行するまでFailedのままでした。
- workerの不足: workerが2つで、2つのTaskが実行中のときに3つ目のTaskを作ると、no free workers availableでFailedになりました。WorkerPoolのreplicasを4に増やしてからax resumeすると、Runningになりました。
- ゴールデンスナップショットの未完成: Task を作ってまもなく Suspend / Resume を実行すると、FailedPrecondition desc = a Golden data resume requires the ActorTemplate golden snapshot, which is not availableでFailedになりました。しばらく待ってからax resumeをやり直すと、Runningになりました。ActorTemplateのゴールデンスナップショットは、Taskの作成後に非同期で作られるため、その完成前にResumeすると失敗する、と考えられます(4.4節のprotoのコメントに合わせた解釈で、作成のタイミングそのものは確認していません)。
$ ax describe task ws-task
Conditions:
TYPE STATUS REASON MESSAGE
WorkspaceReady True SetupComplete Workspace setup completed at 10.244.0.24
Ready False ActorResumeFailed resuming actor default/ws-task: rpc error: code = FailedPrecondition desc = a
1 like
An Overview of Agent Substrate
A Kubernetes Pod is a useful unit of deployment. An agent session has a different lifetime: it may run briefly, wait for minutes, then need its process and filesystem state back for the next turn. Keeping a dedicated environment resident through every wait can consume capacity that other sessions could use.
Agent Substrate separates those lifetimes. Kubernetes runs a pool of worker Pods; Substrate places sandboxed agent environments onto them, persists their state, and restores them when work arrives. This article follows one request through that system, then examines the components and a Counter demo you can try locally.
TL;DR
- Kubernetes manages Workers; Substrate manages Actors. Actor state and assignments live behind a separate API backed by PostgreSQL.
- Workers can serve many Actors over time. Capacity-based concurrency is combined with suspend/resume to reuse compute.
- atelet and ateom save and restore sandbox state. They use gVisor or microVMs; the snapshot fidelity determines what is preserved.
- Credentials can stay outside the Actor. An egress gateway and credential provider can inject API credentials into outgoing requests.
- Check the implemented behavior, not just the design. Authorization enforcement is experimental and disabled by default for ordinary Control RPCs. Idle detection and performance claims also require context.
Implementation details target Agent Substrate v0.5.0. See [Components and versions](#components-and-versions) for the full scope.
This walkthrough assumes familiarity with Pods, Deployments and basic Kubernetes networking. Start with the [request lifecycle](#follow-a-request-into-a-suspended-actor), jump to the [kind demo](#try-the-counter-demo-with-kind), or use the [evaluation questions](#what-to-evaluate-in-your-own-workload) to assess the fit for your platform.
The design decision: keep the workers, move the sessions
An agent may execute code briefly, wait for inference or an external tool, then wait indefinitely for another user turn. Running untrusted code also creates a need for isolated environments, often with one tenant per sandbox.
The combination matters: a large fleet can contain many more waiting environments than actively executing ones. Reserving CPU and memory for every waiting agent wastes capacity. Snapshot-based multiplexing is intended to let those environments retain state without remaining assigned to running compute.
Source: docs/architecture.md#L17-L23.
What Kubernetes still does—and what Substrate moves elsewhere
The architecture document identifies three pressures behind its design:
| Pressure | Why it matters for agents |
|:---|:---|
| API resource count and write traffic | Millions of frequently changing agent records are a different workload from ordinary Kubernetes reconciliation |
| Activation latency | Pod scheduling, reconciliation, network hops and image preparation add overhead to very short executions |
| State movement | Rapidly saving and restoring many environments is a different problem from attaching conventional persistent volumes |
These are the project's design judgments for its intended scale, rather than universal Kubernetes limits or a claim that every Pod takes seconds to start.
Kubernetes still handles infrastructure provisioning and worker Pod lifecycles. Substrate introduces a separate control plane for Actor state, assignment and resumption, so an activation does not need to wait for a fresh Pod lifecycle.
Sources: docs/architecture.md#L153, docs/architecture.md#L160-L172.
The important boundary is the activation path: a new turn can restore an Actor onto an existing Worker without provisioning another Pod. Database write throughput, reconciliation and environment preparation all contribute to that choice.
Follow a request into a suspended Actor
The execution layer combines routing, state persistence and sandbox isolation:
!Agent Substrate adds a gateway, state persistence and kernel isolation above Kubernetes, compute and storage.
Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).
Two lifecycles, two APIs
| Resource | Meaning | Managed through |
|:---|:---|:---|
| ActorTemplate | Immutable workload definition, including images and snapshot settings | Substrate API and database |
| WorkerPool | Defines a pool that atecontroller reconciles into a Deployment | Kubernetes CRD |
| Atespace | Logical grouping and identity scope for Actors; not a Kubernetes namespace | Substrate API and database |
| Actor | An instance of an ActorTemplate and the unit of suspend/resume | Substrate API and database |
| Worker | A record corresponding to a worker Pod; can host multiple Actors concurrently | Substrate API and database |
Sources: docs/glossary.md#L10-L23, docs/glossary.md#L38-L52.
An Actor is the stateful environment that belongs to a session. A Worker is the reusable execution capacity. Suspending an Actor releases its assignment; it does not require destroying and recreating the worker Pod. Worker capacity can also permit several Actors to run concurrently.
Restore first, forward second
For an HTTP request targeting a suspended Actor, the activation path has four steps:
1. The router identifies the Actor from ate-target-actor.
2. The control plane assigns a Worker and coordinates restoration of the Actor's snapshot.
3. Once the assignment is ready, the router forwards the request through the Worker's tunnel to the Actor.
4. The Actor handles the request and returns its response.
For an Actor that is already running, ResumeActor returns the existing assignment. The call is idempotent.
Source: workflow_resume.go.
Returning a response does not automatically suspend the Actor. The application or higher-level system must choose a safe suspension boundary, taking account of outstanding work and network calls.
Suspend and resume
!Active agent environments on Workers exchange state with idle environments persisted in storage through suspend/resume.
Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).
Here, “idle” means state has been persisted and the environment removed from running compute, rather than a process simply sleeping. The illustration is conceptual: it does not imply a fixed one-to-one relationship between Workers and Actors.
When a request arrives, atenet-router asks the control plane to resume the target Actor and resolve its Worker assignment. A suspended Actor is restored from its latest snapshot before traffic is forwarded. It may resume on a different Worker.
The router itself does not decide whether the Actor is running; that decision belongs to the control plane. Suspending the Actor saves state and releases its assignment for reuse.
Source: docs/architecture.md#L193-L204.
What runs where
| Component | Responsibility | Relationship to Kubernetes |
|:---|:---|:---|
| ate-api-server (ateapi) | Actor lifecycle, scheduling and snapshot coordination | Separate API, scheduler and workflow layer backed by PostgreSQL |
| atecontroller | Reconciles WorkerPool and related CRDs | Standard controller-runtime controller |
| atelet / ateom | Coordinates and executes sandbox checkpoint/restore | Additional execution layer inside worker Pod boundaries |
| atenet router and egress | Resumption-aware ingress and controlled outbound traffic | Routing plus Actor lifecycle coordination |
| podcertcontroller | Issues short-lived certificates for component mTLS | Signer controller for PodCertificateRequest |
The threat-model diagram shows how these components connect:
!Substrate architecture showing ate-api-server, atelet, ateom, atenet-router, object storage and PostgreSQL.
Source: docs/threat-model.md#L30-L40.
{% details Repository map: controllers, sidecars and tools %}
The repository also includes supporting programs:
| Directory | Role |
|:---|:---|
| cmd/ateapi | gRPC control plane |
| cmd/atecontroller | Kubernetes controllers |
| cmd/atelet | Node supervisor deployed as a DaemonSet |
| cmd/ateom-gvisor | Drives gVisor inside worker Pods |
| cmd/ateom-microvm | Runs Actors with Cloud Hypervisor microVMs |
| cmd/objectstore-plugin | Snapshot transfer sidecar for node and control-plane operations |
| cmd/atenet | Networking daemon and proxy integration |
| cmd/dataplane/envoy | Envoy image with Rust Dynamic Modules for egress enforcement |
| cmd/credential-provider/kubernetes-secrets | Reference credential provider backed by Kubernetes Secrets |
| cmd/podcertcontroller | Pod certificate signers |
| cmd/kubectl-ate | Actor and other Substrate resource operations |
| cmd/ate-setup | Installation and removal |
| cmd/benchmarking | Synthetic resource-consuming test workloads |
| tools/setup-gcp | GKE, storage and IAM provisioning |
The README's tour is not an exhaustive inventory; the sidecar, provider, dataplane and setup entries are supplemented from their own source and documentation.
Sources: README.md#L260-L272, cmd/objectstore-plugin/main.go#L15-L19, cmd/ate-setup/README.md#L3, cmd/dataplane/envoy/README.md.
{% enddetails %}
Resource ownership and assignment
!Agent Substrate between the agent application layer and Kubernetes, with sandbox isolation and storage beneath it.
Source: Bringing you Agent Sandbox on GKE and Agent Substrate, Google Cloud Blog (May 20, 2026).
The green sandbox-isolation block is the foundation for the checkpoint and restore operations. Agent Substrate incorporates Agent Sandbox's secure runtime and snapshot capabilities into a separate execution layer.
classDiagram
namespace kube-apiserver {
class WorkerPool {
<<CRD>>
}
class Deployment
class WorkerPod {
ateom
runsc
}
}
namespace ate-api-server {
class ActorTemplate {
<<record>>
}
class Actor {
<<record>>
status
snapshotRefs
}
class Worker {
<<record>>
actorAssignments
podIP
}
}
ActorTemplate ..> WorkerPool : worker_selector (labels)
WorkerPool ..> Deployment : reconciled by atecontroller
Deployment "1" *-- "*" WorkerPod : manages
Actor "*" --> "1" ActorTemplate : actor_template
Actor "0..*" --> "0..1" Worker : runs on
Worker "1" --> "1" WorkerPod : maps to
Adapted from docs/architecture.md#L268-L303; the multiplicity reflects the implementation's support for multiple Actors per Worker.
The split between API servers is central. Infrastructure resources stay in Kubernetes, while frequent Actor/Worker assignment changes go through ate-api-server and its state store.
Two resource layers
The architecture gives three reasons for this split:
- Keep high-frequency Actor updates away from the Kubernetes control plane.
- Use low-latency lookups and atomic assignments for resumption. The document's 100ms figure is a design target, not a result measured here.
- Retain Kubernetes RBAC, auditing and policies for infrastructure resources such as WorkerPools and SandboxConfigs.
One detail in that document is stale: it says authorization has not been implemented. The same v0.5.0 tag contains OpenFGA-based authorization. --experimental-enable-authz defaults to false and controls enforcement for ordinary Control RPCs. AccessPolicy RPCs are checked regardless of that flag.
Sources: docs/architecture.md#L247-L261, cmd/ateapi/main.go#L84-L88, cmd/ateapi/internal/authz/interceptor.go#L28-L60.
ate-api-server: the Actor control plane
ateapi owns Actor lifecycles, schedules them onto Workers and coordinates snapshots. Its gRPC server connects to PostgreSQL, creates Kubernetes clients and configures its mTLS credentials.
Sources: docs/glossary.md#L56-L58, cmd/ateapi/main.go#L509-L521, cmd/ateapi/main.go#L560-L586.
templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv, *templateResyncInterval)
templateReconciler.Start(shutdownCtx)
workerAssignmentReconciler := controlapi.NewWorkerAssignmentReconciler(persistence, workerCache)
workerAssignmentReconciler.Start(shutdownCtx)
Source: cmd/ateapi/main.go#L326-L330.
Despite their names, these reconcilers are internal Substrate loops rather than controller-runtime reconcilers watching custom resources. ActorTemplateReconciler prepares golden snapshots; WorkerAssignmentReconciler handles Actors lost when ateom restarts.
This component does not map one-to-one to kube-apiserver: it combines Actor-specific API handling, scheduling and workflows.
atecontroller: conventional Kubernetes reconciliation
atecontroller uses ctrl.NewManager and registers four reconcilers, including WorkerPoolReconciler. Its core structure is familiar:
func (r *WorkerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
wp := &atev1alpha1.WorkerPool{}
if err := r.Get(ctx, req.NamespacedName, wp); err != nil {
if k8errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, fmt.Errorf("failed to get worker pool %q: %w", req.NamespacedName, err)
}
if !wp.GetDeletionTimestamp().IsZero() {
log.Info("WorkerPool is being deleted")
return ctrl.Result{}, nil
}
if err := r.reconcileWorkerPool(ctx, wp); err != nil {
log.Error(err, "Failed to reconcile worker pool")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
Sources: cmd/atecontroller/main.go#L233-L276, cmd/atecontroller/internal/controllers/workerpool_controller.go#L105-L129.
Reconciliation does more than produce a Deployment. The WorkerPool reconciler protects referenced SandboxConfigs with finalizers; a separate SandboxConfigProtectionReconciler releases protection. Updating the recorded SandboxConfig uses two status writes so that status never references a configuration whose protection has already been released.
Owns(&Deployment{}) and Watches(&SandboxConfig{}, ...) connect these resources through normal controller-runtime mechanisms.
Sources: cmd/atecontroller/internal/controllers/workerpool_controller.go#L134-L136, cmd/atecontroller/internal/controllers/workerpool_controller.go#L416-L425.
atelet and ateom: checkpoint and restore
atelet runs on each node as a DaemonSet. ateom runs inside worker Pods and exposes operations such as RunWorkload, CheckpointWorkload and RestoreWorkload.
atelet coordinates worker supervision, image caching and CSI volumes. It also reuses kubelet's image credential-provider mechanism through --image-credential-provider-config.
Sources: docs/architecture.md#L324-L326, cmd/atelet/main.go#L103-L104.
Snapshot upload and download are delegated to objectstore-plugin sidecars over a Unix-socket gRPC API:
| Plugin role | Operations |
|:---|:---|
| NodeProvider, alongside atelet | Upload and fetch snapshot files |
| ControlProvider, alongside ate-api-server | Copy and delete whole snapshots |
The main processes do not directly transfer snapshots to object storage. This does not mean they hold no credentials at all: mTLS and database authentication remain separate concerns.
Source: docs/architecture.md#L330.
For gVisor, cmd/ateom-gvisor/runsc.go invokes runsc checkpoint and runsc restore. The microVM implementation uses Kata integration and Cloud Hypervisor, with userfaultfd demand paging when restoring memory.
Sources: docs/architecture.md#L328-L338.
Kubelet and the container runtime still run the worker Pod itself. Substrate manages sandboxed Actors inside it. Kubernetes already has a Kubelet Checkpoint API; Substrate's distinction is coordinating persistence, assignment, restore and reconnection as an Actor lifecycle.
atenet: routing that can resume its destination
Ingress requests identify an Actor with the ate-target-actor header, formatted as <atespace>/<actor>. In the Envoy configuration described here, an ext_proc processor calls the control plane's ResumeActor RPC before resolving the destination.
Once the assignment is available, the router opens an mTLS CONNECT tunnel to the Worker's atunnel listener on port 8443. atunnel, hosted by ateom, forwards traffic over the Actor's private veth interface. Worker Pod port 80 is not a direct Actor ingress endpoint.
Sources: docs/architecture.md#L344-L354.
The Kubernetes analogy is Service/Ingress/Gateway routing with an extra orchestration step: restore the destination if necessary before forwarding to it.
podcertcontroller: Pod certificate signers
podcertcontroller provides three signers:
| Signer | Purpose |
|:---|:---|
| servicedns.podcert.ate.dev/identity | Service DNS certificates backed by a local CA |
| podidentity.podcert.ate.dev/identity | Pod identity certificates equivalent to Kubernetes service account identity |
| postgres.podcert.ate.dev/identity | PostgreSQL login certificates for ate-api-server |
Source: cmd/podcertcontroller/main.go#L15-L22.
It watches certificates.k8s.io/v1 PodCertificateRequest objects. The API became GA in Kubernetes v1.37:
PodCertificateRequest: {
{Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha},
{Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta},
{Version: version.MustParse("1.37"), Default: true, PreRelease: featuregate.GA}, // LockToDefault in 1.38
},
Source: kube_features.go.
The API lets kubelet request certificates and project them into Pods. A signer must still process those requests. The well-known kubernetes.io/kube-apiserver-client-pod signer described in the API comments is unimplemented in this version.
Substrate fills its own needs with the three signers above. Its README calls the controller a “polyfill” for the missing signers.
Sources: certificate API types, README.md#L268-L269.
Keep session state and API credentials separate
Isolation, identity and credentials are separate controls
!The agent environment combines identity-based ingress and egress, secrets management, kernel isolation and rapid suspend/resume.
Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).
Kernel isolation does not by itself protect external API credentials. That also requires the gateway and credential-provider configuration described below.
The security document distinguishes Met, In Progress, Future and Out of Scope. Selected principles in v0.5.0 are:
| Principle | Meaning | Documented status |
|:---|:---|:---|
| Always Sandboxed | Run Actors in gVisor or Cloud Hypervisor microVMs rather than relying on ordinary Linux containers alone | Met |
| Zero Ambient Credentials | Do not implicitly give Actors infrastructure permissions or external API keys | Met; distinct from demos that explicitly pass keys in environment variables |
| No Access by Default | Restrict Actor access to the control plane, separate from required infrastructure traffic | Loopback/private netns: Met; DNS filtering: In Progress |
| Fail-Closed Policy-Enforced Egress Gateway | Control outbound traffic through an egress proxy | Gateway: Met; fail-closed behavior: In Progress, with UDP passthrough noted |
Sources: docs/security.md#L30-L33, docs/security.md#L41, docs/security.md#L59, docs/security.md#L75-L77.
Warning: The same document lists Atespace authorization as In Progress, and node blast-radius containment and storage encryption as Future. The security architecture is not evidence that every control is fully implemented. Ordinary Control RPC authorization still requires the experimental flag.
Inject credentials outside the Actor
With egress credential injection, an Actor sends an HTTPS request containing a placeholder header. The gateway obtains the real credential and substitutes it outside the sandbox:
1. The Actor sends the request with a placeholder value.
2. The egress gateway terminates TLS and matches an EgressPolicy header-replacement rule.
3. The credential provider authorizes access and reads the secret.
4. The gateway replaces the header and makes a separate TLS connection to the destination.
The Actor must trust the gateway's CA. If the request does not contain the header to replace, credential lookup and replacement do not occur.
This keeps the API key outside Actor memory and snapshots. It does not prevent misuse of an allowed API; destination restrictions and application authorization still matter.
Sources: docs/egress-credential-injection.md#L1-L15, docs/egress-credential-injection.md#L41-L54.
The reference kubernetes-secrets provider starts with an empty deny-all policy. Readable namespaces must be explicitly allowed for each Atespace. Actors do not read Kubernetes Secrets themselves.
Source: README.md#L132-L136.
EgressPolicy defines destinations and header replacement
An EgressPolicy specifies allowed traffic and header replacement rules. The credential provider separately decides whether the Actor can use the referenced secret. The API defines the policy as an unordered set of allows: traffic matching no rule is denied.
Source: pkg/proto/ateapipb/ateapi.proto#L609-L610.
For example, allow HTTPS to api.example.com and replace its Authorization header:
rules:
- https:
hostnames: ["api.example.com"]
effects:
replaceHeaders:
- header: Authorization
prefix: "Bearer "
credentialUri: ate-secret://k8s.io/default/ns1/example-api/token
Source: docs/egress-credential-injection.md#L69-L76.
The policy is associated with an Actor rather than embedded in its ActorTemplate:
# Create an egress policy.
kubectl ate create egress-policy <actor-name> -a <atespace> -f policy.yaml
Source: cmd/kubectl-ate/README.md#L247-L253.
Note: The Claude Code demo discussed later directly places ANTHROPIC_API_KEY in the ActorTemplate environment. That differs from gateway injection. In either case, the Actor needs an egress policy permitting its HTTPS destination, such as api.anthropic.com.
The complete request path
With routing and credential injection in place, an HTTP-triggered agent can resume and call an external LLM as follows. This combines the documented flows; Counter itself does not call an LLM.
sequenceDiagram
actor User as User / higher-level system
participant Router as atenet-router
participant API as ate-api-server
participant Atelet as atelet
participant Tunnel as ateom / atunnel
participant Agent as Actor
participant Egress as atenet-egress
participant Cred as credential provider
participant LLM as External LLM API
User->>Router: HTTP request (ate-target-actor)
Router->>API: ResumeActor
alt Actor is suspended
API->>Atelet: Restore request
Atelet->>Tunnel: RestoreWorkload
Tunnel-->>Atelet: Restore complete
Atelet-->>API: Complete
else Actor is running
Note over API: Return existing assignment
end
API-->>Router: Worker assignment
Router->>Tunnel: Forward through mTLS CONNECT
Tunnel->>Agent: Forward request
Agent->>Egress: HTTPS (placeholder header)
Egress->>Cred: Request credential
Cred-->>Egress: Return API key after authorization
Egress->>LLM: Replace header and send over a separate TLS connection
LLM-->>Egress: Response
Egress-->>Agent: Response
Agent-->>Tunnel: Result
Tunnel-->>Router: Response
Router-->>User: Response
Note over User,API: A higher-level system can request SuspendActor to save state and release the assignment
Sources: Resume sequence, credential injection. Cold boot without a snapshot and other branches are omitted.
The Actor-to-gateway and gateway-to-provider connections use separate TLS sessions. The gateway must terminate the first to replace headers in the second.
Try the Counter demo with kind
The Counter workload makes state preservation visible: each request increments counters in process memory, a file and the root filesystem. The example below uses gVisor and includes recorded output from a local kind run.
Set up the cluster and demo
Building v0.5.0 requires Go 1.27.0 and a working Docker-based development environment. The microVM option additionally requires /dev/kvm and suitable virtualization support.
Sources: go.mod#L1-L5, hack/create-kind-cluster.sh#L74-L87.
Use a dedicated checkout and cluster name. The creation script deletes and recreates an existing cluster with the same name, so choose one that does not overlap with an environment you want to keep.
git clone --branch v0.5.0 --depth 1 \
https://github.com/agent-substrate/substrate.git substrate-v0.5.0
cd substrate-v0.5.0
export KIND_CLUSTER_NAME=ate-overview
# create cluster and local registry (IPv4; IP_FAMILY=dual|ipv6 overrides)
hack/create-kind-cluster.sh
# install ate, PostgreSQL, rustfs
hack/install-ate-kind.sh --deploy-ate-system \
--credential-provider='{"name":"k8s.io"}'
# install counter demo
hack/install-ate-kind.sh --deploy-demo-counter
# install kubectl-ate
go install ./cmd/kubectl-ate
# create a counter actor in the demo's atespace (--template names the
# actor template, resolved in the actor's atespace)
kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter
# port-forward the network router to bind to local port `8000`
kubectl port-forward -n ate-system svc/atenet-router 8000:80
Source: README.md#L111-L129.
The creation script sets up a local registry and a kind cluster with kindest/node:v1.37.0. It explicitly enables certificate-related feature gates, including ClusterTrustBundleProjection; some comments still describe v1.36 requirements.
Source: hack/create-kind-cluster.sh#L104-L113.
The system installation uses ko to build and push its Go components and deploys PostgreSQL, RustFS, CRDs and SandboxConfig resources. The Counter installation adds its WorkerPool and ActorTemplate and waits for a golden snapshot.
WorkerPool and ActorTemplate
The demo's WorkerPool includes the following settings. The installer resolves template placeholders and ko:// references before applying the configuration.
apiVersion: ate.dev/v1alpha1
kind: WorkerPool
metadata:
name: counter
namespace: ate-demo-counter
labels:
workload: counter
spec:
replicas: 3
workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor
sandboxClasses:
- name: gvisor
template:
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 250m
memory: 1Gi
Source: demos/counter/counter.yaml.tmpl#L26-L63.
An ActorTemplate is not a Kubernetes object. This YAML maps to Substrate's protobuf fields and is passed through kubectl ate create actor-template:
metadata:
atespace: ate-demo-counter
name: counter
workerSelector:
matchLabels:
workload: counter
containers:
- name: counter
image: ko://github.com/agent-substrate/substrate/demos/counter
wakeupProbe:
httpGet:
path: /readyz
port: 80
volumeMounts:
- name: data
mountPath: /home/counter
resources:
limits:
- name: cpu
quantity: "1"
- name: memory
quantity: 512Mi
snapshotConfig:
preferredFidelity: SNAPSHOT_FIDELITY_MEMORY
storageLocation: gs://${BUCKET_NAME}/ate-demo-counter/
sandboxConfig:
sandboxClass: gvisor
configName: gvisor-default
volumes:
- name: data
durableDir: {}
Source: demos/counter/counter-template.yaml.tmpl#L22-L69.
SNAPSHOT_FIDELITY_MEMORY requests preservation of process memory. gvisor-default names a cluster-scoped SandboxConfig whose assets specify where to download the runtime binaries:
apiVersion: ate.dev/v1alpha1
kind: SandboxConfig
metadata:
name: gvisor-default
annotations:
sandboxconfig.ate.dev/is-class-default: "true"
spec:
sandboxClass: gvisor
defaultVersion: v3
versions:
- name: v3
state: Enabled
pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4"
assets:
arm64:
gvisor:
url: "gs://gvisor/releases/nightly/2026-10-10/aarch64/gvisor.tar.zstd"
sha256: "aaf414f06eea7960238e43165c691f0a36cefc0593fb141e011e96211c7b6f57"
Source: manifests/ate-install/sandboxconfig-gvisor.yaml#L22-L48. Only the arm64 assets are shown; amd64 assets are omitted.
Observe Actor state and preserved counters
A newly created Counter Actor starts suspended: its snapshot exists, but it has no Worker assignment yet.
kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter
Recorded output:
ATESPACE NAME TEMPLATE STATE WORKER POD WORKER IPS VERSION AGE
ate-demo-counter my-counter-1 ate-demo-counter/counter ACTOR_STATE_SUSPENDED <none> 1 0s
Send a request through the router. It resumes the Actor before forwarding, and the recorded response is HTTP 200:
curl -X POST \
-H "ate-target-actor: ate-demo-counter/my-counter-1" \
-i http://localhost:8000/
Recorded output:
HTTP/1.1 200 OK
Content-Length: 111
Content-Type: text/plain; charset=utf-8
hello from: 169.254.17.2 | preserved memory count: 1 | preserved file counter: 1 | preserved rootfs counter: 1
A second POST increments the counter to 2. The Actor is now running and assigned to a worker Pod:
curl -X POST \
-H "ate-target-actor: ate-demo-counter/my-counter-1" \
http://localhost:8000/
kubectl ate get actor my-counter-1 -a ate-demo-counter
Recorded output:
ATESPACE NAME TEMPLATE STATE WORKER POD WORKER IPS VERSION AGE
ate-demo-counter my-counter-1 ate-demo-counter/counter ACTOR_STATE_RUNNING ate-demo-counter/counter-796d677995-v55tv 10.244.0.23 3 20s
Explicitly suspend it, then send another request:
kubectl ate suspend actor my-counter-1 -a ate-demo-counter
curl -X POST \
-H "ate-target-actor: ate-demo-counter/my-counter-1" \
http://localhost:8000/
Recorded output:
hello from: 169.254.17.2 | preserved memory count: 3 | preserved file counter: 3 | preserved rootfs counter: 3
The memory, file and root-filesystem counters continue at 3. This illustrates the documented snapshot-and-resume lifecycle, including preserved process memory.
Stop the port-forward when finished, then remove the dedicated cluster. The kind-registry container is separate and remains after cluster deletion.
hack/kind.sh delete cluster --name ate-overview
Plan the deployment around more than Pods
Worker Pods and CRDs are only part of the deployment. The following table distinguishes infrastructure that must be supplied from components the installer can deploy:
| Dependency | Purpose | Installation considerations |
|:---|:---|:---|
| PostgreSQL | Actor/Worker state store | The kind setup includes a development database; production operators supply database identities, schema and connection settings |
| GCS or S3-compatible object storage | Durable snapshot storage | Configure storage.bucketName / BUCKET_NAME; kind uses ate-snapshots and an in-cluster RustFS service |
| Egress credential provider | Resolve credentials outside Actors | Provider selection is required, including explicit {"enabled":false} to disable injection; {"name":"k8s.io"} deploys the reference provider |
| Sandbox runtime binaries | Run Actors with gVisor or microVMs | Selected through a cluster-scoped SandboxConfig; default gVisor assets come from a public GCS bucket |
| Proxy dataplane image | Router and egress proxies | --atenet-dataplane selects Envoy, requiring its custom build, or agentgateway, using a published image |
| Object-storage permissions | Let plugin sidecars transfer snapshots | For GCS, configure the required Workload Identity/IAM bindings; the kind substitute uses local credentials |
| CSI driver, when needed | Attach CSI-backed persistent storage to Actors | Not needed for the demo's durableDir; Substrate imposes additional controller/node deployment requirements |
External databases are not provisioned by ateapi or ate-setup. Snapshot-bucket selection and credential-provider selection also need explicit configuration, even when other defaults are available.
Sources: docs/postgres.md#L8, docs/operator-install.md#L314, docs/egress-credential-injection.md#L128-L131.
Missing GCS IAM bindings can produce a 403 on the first suspend/resume, and also prevent control-plane snapshot operations and image pulls.
Source: tools/setup-gcp/README.md#L154-L159.
Note: Substrate directly orchestrates CSI operations from ateapi and atelet. A CSI controller must be reachable as a cluster-network gRPC service, unlike the usual controller-side Unix socket arrangement. See docs/csi-deployment.md#L5.
For the Kubernetes v1.37 setup covered here, Pod certificate and ClusterTrustBundle features are GA and enabled by default. Substrate also includes its certificate controller. Provider-specific installation requirements should still be checked separately.
Source: ClusterTrustBundle feature definition.
GKE availability
Google announced Agent Substrate availability on GKE on September 16, 2026.
The GKE offering installs open-source Substrate onto GKE Standard; Autopilot is not supported. Evaluation and non-production use are available, while production support is allowlisted under a private GA program. Substrate has no additional product charge, but the GKE and other resources created still incur their normal charges.
Source: Install Agent Substrate on GKE.
The announcement also describes ComputeClasses and optional Filestore agent volumes. Shared workspace storage serves a different purpose from snapshots of execution state.
Google reports sub-500ms resumption, more than 500 suspend/resume activations per second and 10x density. These are Google's published figures, separate from upstream design targets and the Counter example.
Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026).
What to evaluate in your own workload
Substrate is worth examining when a fleet has many isolated, stateful environments but only a fraction need to execute at once. The useful question is how much resident capacity suspend/resume can release, and whether restoration meets the application's latency needs.
Start with three checks:
- Suspension boundaries: identify when an environment can safely stop, including outstanding network calls and application work.
- State requirements: decide what must survive, then select snapshot fidelity and any persistent workspace storage accordingly.
- Operational ownership: account for the database, snapshot backend, identity and egress services as well as the Workers.
The Counter demo exposes the core mechanism. An agent platform must then connect it to real session events and measure its own activation latency and capacity use.
Further implementation details
{% details Claude Code demo: workload execution versus UI state %}
What the Claude Code demo actually implements
claude-code-multiplex defines three Claude Code Actors—luna, mars and orion—with a two-Pod WorkerPool. It uses the control API rather than sending workload HTTP through atenet-router, so it does not use the routing header.
Source: demos/claude-code-multiplex/README.md#L14-L21.
An ActorTemplate supplies the task and interval as environment variables:
env:
- name: ACTOR_NAME
value: "luna"
- name: TASK
value: "Tell me one short, surprising fact about the Moon. One sentence."
- name: INTERVAL_SECONDS
value: "45"
- name: ANTHROPIC_API_KEY
value: "${ANTHROPIC_API_KEY}"
Source: demos/claude-code-multiplex/agent-luna-template.yaml.tmpl#L34-L42.
The workload repeatedly runs claude --print and then sleeps:
while true; do
...
claude --print "${TASK}" 2>&1 || echo "[demo-actor:${ACTOR_NAME}] claude exited non-zero"
...
sleep "${INTERVAL_SECONDS}"
done
Source: demos/claude-code-multiplex/workload/run.sh#L17-L49.
That idle interval is an opportunity for multiplexing. However, the v0.5.0 UI does not implement everything its README's task-flow description suggests:
| UI behavior | Implementation |
|:---|:---|
| “Give a task” | Records the selected Actor and task in UI-server assignment state |
| queued / running / completed | Derived from timestamps and randomized waiting/execution durations |
| Actor state and logs | Retrieved from the Substrate and Kubernetes APIs |
giveTask does not send a task to the Actor or call ResumeActor / SuspendActor. A comment in computeState identifies the task state as a time-based UI display rather than Substrate state.
Source: demos/claude-code-multiplex/ui/server.go#L252-L315.
A changing UI status therefore does not establish that a real LLM task completed or automatic multiplexing occurred. The useful pieces are the ActorTemplates, the periodic Claude Code workload and the state-viewing UI. Verify suspend/resume through real API operations and Actor state, as in the [Counter demo](#try-the-counter-demo-with-kind).
{% enddetails %}
{% details RustFS: the local snapshot backend %}
RustFS
RustFS is a separate, Rust-based S3-compatible object store. The kind installation uses it as its local snapshot backend. The v0.5.0 manifest selects rustfs/rustfs:1.0.0-beta.3.
This development setup does not replace production storage availability, persistence or operational design. objectstore-plugin uses RustFS through its S3 backend; GCS uses the same plugin interface with a different backend.
Sources: manifests/ate-install/kind/rustfs.yaml#L55-L75, rustfs/rustfs.
{% enddetails %}
{% details ReBAC: deriving permissions from relationships %}
ReBAC and Kubernetes RBAC
Substrate's API authorization uses OpenFGA's relationship-based access control model. In this version, ordinary Control RPC enforcement remains experimental and disabled by default.
Source: docs/security.md#L46.
Kubernetes RBAC checks applicable RoleBindings and ClusterRoleBindings against Role/ClusterRole rules: API groups, resources, verbs, namespaces and sometimes resource names. It does not automatically derive access from ownership or arbitrary parent-child relationships.
Source: Using RBAC Authorization.
ReBAC derives access from relations between subjects and objects, and potentially relations between objects. Inheritance is explicit in the authorization model, rather than automatic for every parent-child association.
Source: OpenFGA: Modeling—Getting Started.
Substrate's .fga model makes a Worker's read permission depend on its parent global object's viewer or controller relation:
<!-- OpenFGA DSL; Python fencing provides partial syntax highlighting only. -->
type worker
relations
define parent_global: [global]
define host_node: [node]
define can_get: viewer from parent_global or controller from parent_global
Source: cmd/ateapi/internal/authz/model.fga#L29-L34.
An Atespace can inherit its owner from its parent global object:
type atespace
relations
define parent_global: [global]
define owner: [user] or owner from parent_global
An ActorTemplate can derive read permission from the viewer relation on its parent Atespace:
type actor_template
relations
define parent_atespace: [atespace]
define can_get: viewer from parent_atespace
Sources: cmd/ateapi/internal/authz/model.fga#L71-L77, cmd/ateapi/internal/authz/model.fga#L101-L106.
The relation data connects global → atespace → actor_template and global → worker. Permission checks follow those modelled relations. The practical difference from ordinary Kubernetes RoleBindings is the ability to derive child access from parent relations according to an explicit model.
{% enddetails %}
Components and versions
| Component | Version | Repository | Scope |
|:---|:---|:---|:---|
| Agent Substrate | v0.5.0 | agent-substrate/substrate | Implementation, configuration and source examples |
| Kubernetes | v1.37.1 | kubernetes/kubernetes | Pod certificate APIs and feature gates |
| kind | v0.33.0 | kubernetes-sigs/kind | Local example using kindest/node:v1.37.0 |
GKE availability information reflects the official documentation checked on October 20, 2026. The GKE installer does not necessarily distribute the same version as the upstream tag used here.
Note: Agent Substrate is pre-1.0 and does not guarantee backward compatibility. Unless another repository is named, source links in this article refer to agent-substrate/substrate v0.5.0.
References
- Kubernetes Podcast #272: Agent Substrate
- Agent Substrate repository
- Bringing you Agent Sandbox on GKE and Agent Substrate
- Agent Substrate on GKE announcement
- Install Agent Substrate on GKE
- Architecture
- Threat model
- Glossary
- Security guide
-
2026 깃허브 오픈소스 AI 에이전트 생태계 총정리
00:00 2026 최신 깃허브 오픈소스 AI 에이전트 생태계 소개
01:40 AI 에이전트 생태계의 전반적인 조감도와 4대 영역 파악
03:05 코어 개발 환경(IDE)과 AI의 융합 및 클로드 코드와 젯브레인 비교
06:08 멀티 에이전트 오케스트레이션과 로플로, 오차드, 서브스트레이트 기술
10:39 과학 연구 및 CAD 생성을 위한 에이전트 특수 스킬 부여
10:45 에이전트 전용 고속 검색 엔진과 지능 확장 메모리
10:50 커널 레벨 샌드박스를 통한 안전한 통제망과 보안 API 라우팅
14:29 헤드리스 디자인 에디터와 미니멀한 프로젝트 관리 공간
16:32 사물 중심의 스큐어모피즘을 적용한 데스크톱 환경
16:37 야간 세션 유지, 저수준 그래픽 제어 및 수학적 검증 유틸리티
19:23 AI 결과물의 품질 관리와 간결하고 직관적인 텍스트 사용자 경험
21:49 에이전트의 사고 과정 시각화와 조율자로서의 개발자 역할 결론
Claude Code Plugins Directory : anthropics/claude-code-plugins
JetBrains Plugin : plugins.jetbrains.com
Scientific Agent Skills : K-Dense-AI/scientific-agent-skills
ruflo : ruvnet/ruflo
ECC : affaan-m/ECC
XERJ : xerj-org/xerj
text-to-cad : earthtojake/text-to-cad
substrate : agent-substrate/substrate
OpenShell : NVIDIA/OpenShell
FreeLLMAPI : tashfeenahmed/freellmapi
Orchard : openai/orchard
OpenPencil : open-pencil/open-pencil
Gr8r Studio : gr8rstudio/gr8r-studio
Insomnia : krishhgg/Insomnia
BRUTAL Framework : ahwootech/BRUTAL
librecalculator : tonyzedginidze/librecalculator
gpt-with-taste : NB-Corp/gpt-with-taste
cc-stretch : pro-vi/cc-stretch
DEsk : fyzugx/DEsk
Remory : fudan-generative-vision/remory
agentplay : Entelligence-AI/agentplay
[en-US] 2026 GitHub Open Source AI Agent Ecosystem Overview
00:00 Introduction to the Latest GitHub Open Source AI Agent Ecosystem (2026)
01:40 An Overview of the AI Agent Ecosystem and Understanding the Four Key Areas
03:05 Convergence of Core Development Environments (IDEs) and AI, and a Comparison of Claude Code and JetBrains
06:08 Multi-Agent Orchestration and Loflow, Orchard, and Substrate Technologies
10:39 Granting Agent Special Skills for Scientific Research and CAD Generation
10:45 Agent-Dedicated High-Speed Search Engine and Intelligent Scalable Memory
10:50 Secure Control Network and Secure API Routing via Kernel-Level Sandbox
14:29 Headless Design Editor and Minimal Project Management Space
16:32 Object-Centric Skeuomorphism Desktop Environment
16:37 Night Session Maintenance, Low-Level Graphics Control, and Mathematical Verification Utilities
19:23 Quality Control of AI Deliverables and Concise and Intuitive Text User Experience
21:49 Visualization of Agent Thought Processes and the Developer's Role as a Coordinator
Conclusion
Claude Code Plugins Directory: anthropics/claude-code-plugins
JetBrains Plugin: plugins.jetbrains.com
Scientific Agent Skills: K-Dense-AI/scientific-agent-skills
ruflo: ruvnet/ruflo
ECC: affaan-m/ECC
XERJ: xerj-org/xerj
text-to-cad: earthtojake/text-to-cad
substrate: agent-substrate/substrate
OpenShell: NVIDIA/OpenShell
FreeLLMAPI: tashfeenahmed/freellmapi
Orchard: openai/orchard
OpenPencil: open-pencil/open-pencil
Gr8r Studio: gr8rstudio/gr8r-studio
Insomnia: krishhgg/Insomnia
BRUTAL Framework: ahwootech/BRUTAL
librecalculator: tonyzedginidze/librecalculator
gpt-with-taste: NB-Corp/gpt-with-taste
cc-stretch: pro-vi/cc-stretch
DEsk: fyzugx/DEsk
Remory: fudan-generative-vision/remory
agentplay: Entelligence-AI/agentplay
[id] Rangkuman Ekosistem AI Agent Open Source GitHub 2026
00:00 Pengantar Ekosistem Agen AI Open Source GitHub Terbaru (2026)
01:40 Gambaran Umum Ekosistem Agen AI dan Memahami Empat Area Utama
03:05 Konvergensi Lingkungan Pengembangan Inti (IDE) dan AI, dan Perbandingan Claude Code dan JetBrains
06:08 Orkestrasi Multi-Agen dan Teknologi Loflow, Orchard, dan Substrate
10:39 Memberikan Keterampilan Khusus Agen untuk Penelitian Ilmiah dan Generasi CAD
10:45 Mesin Pencari Berkecepatan Tinggi Khusus Agen dan Memori Cerdas yang Dapat Diperluas
10:50 Jaringan Kontrol Aman dan Perutean API Aman melalui Sandbox Tingkat Kernel
14:29 Editor Desain Tanpa Antarmuka Pengguna dan Ruang Manajemen Proyek Minimal
16:32 Lingkungan Desktop Skeuomorfisme Berpusat pada Objek
16:37 Pemeliharaan Sesi Malam, Kontrol Grafis Tingkat Rendah, dan Verifikasi Matematis Utilitas
19:23 Kontrol Kualitas Hasil AI dan Pengalaman Pengguna Teks yang Ringkas dan Intuitif
21:49 Visualisasi Proses Berpikir Agen dan Peran Pengembang sebagai Koordinator
Kesimpulan
Direktori Plugin Claude Code: anthropics/claude-code-plugins
Plugin JetBrains: plugins.jetbrains.com
Keterampilan Agen Ilmiah: K-Dense-AI/scientific-agent-skills
ruflo: ruvnet/ruflo
ECC: affaan-m/ECC
XERJ: xerj-org/xerj
text-to-cad: earthtojake/text-to-cad
substrate: agent-substrate/substrate
OpenShell: NVIDIA/OpenShell
FreeLLMAPI: tashfeenahmed/freellmapi
Orchard: openai/orchard
OpenPencil: open-pencil/open-pencil
Gr8r Studio: gr8rstudio/gr8r-studio
Insomnia: krishhgg/Insomnia
BRUTAL Framework: ahwootech/BRUTAL
librecalculator: tonyzedginidze/librecalculator
gpt-with-taste: NB-Corp/gpt-with-taste
cc-stretch: pro-vi/cc-stretch
DEsk: fyzugx/DEsk
Remory: fudan-generative-vision/remory
agentplay: Entelligence-AI/agentplay
7 likes · 544 views
Mentions
Chrome is pretty efficient, as far as per page costs go! If your architecture isn't trash! The sin is this ridiculous era of every program shipping its own runtime. I'm headed for real down votes, but this is the same lazy culture that's taken over everywhere: go and rust both ship these huge statically compiled binaries. When you are a company, you're only running a handful of different workloads/apps on any given box, so the statically compiled price doesn't bite you. But as users, going back to dynamically linking and making the incremental cost of loading an app not also have to carry the entire world with it is such an obvious return, and one that alas seems to not be in progress at all. The agentic age is, I hope, going to question a lot of old assumptions & needs about what we want to run. Having lots of small resident little user agencies, compiled small scripts that are attributed to a user, is something I believe will happen, but I'm less convinced we'll recognize the face of it as it happens, that the tales will be told. Tools like agent-substrate, ax, scion, are this interesting mix between agents, serverless, and kubernetes. I find it fascinating. (For hire on this.) agent-substrate/substrate
@_mctrinh Interesting… it sounds like a kubernetes for AI agents to me? I think google is already working on Agent Substrate. Does this align with what you had in mind? agent-substrate/substrate
Instead of dedicating a pod to each agent, Agent Substrate multiplexes mostly-idle workloads across shared workers with sub-second resume. agent-substrate/substrate
Very TBD, upcoming, early. But. Works like Agent Substrate are a new very lightweight way to allow a colossal number of ultra small processes. There's a missing middle but at a high level users are launching their own little resident ultralight processes into the cloud via this. Grid compute!
Copied @webdevcody but wanted to try ax + agent substrate, pi, and deepseek on a single linode instance running the sandboxes. Also orchestrated by Opus 5.5 OpenBot. Video edited by a team of OpenBots. 300 buildings. google/ax agent-substrate/substrate t.co
@fatih I'm closely following this agent-substrate/substrate
Built for gVisor and microVMs, Agent Substrate multiplexes idle agents onto a small worker pool instead of running a dedicated pod for each. agent-substrate/substrate Anyone running this in production yet?
Agent Executorの概要 はじめに Googleは2026年5月、Agent Substrateと同じ日に、エージェントの実行・再開・分散デプロイのためのオープンソースのランタイム標準としてAgent Executorを発表しました。以前、Agent Substrateの概要で整理したAgent Substrateは、エージェントを少数のworkerへ多重化する実行基盤でした。Agent Executorは、その上でエージェントの実行を管理する側にあたります。 リポジトリ名とCLI名はaxで、READMEの見出しもAXです。ブログではAgent Executorと呼ばれており、ブログからのリンク先はこのリポジトリ(github.com/google/ax)です。 この記事では、Agent Executor v0.3.1 が提供する機能と、アーキテクチャ、Task・Workspace・Modelという3つのリソース、TaskがAgent SubstrateのActorになるまでの流れを整理します。あわせて、kindクラスタで実際に動かして確認した結果も載せています。 対象コンポーネントとバージョン | コンポーネント | バージョン | リポジトリ | 本記事での役割 | |---|---|---|---| | Agent Executor(AX) | v0.3.1 | google/ax | 本記事全体のソースコード調査対象 | | Agent Substrate | 672533541dbf | agent-substrate/substrate | AX v0.3.1のgo.modが固定しているコミット。kindでの検証にもこのコミットを使用 | | kind | v0.33.0 | kubernetes-sigs/kind | 検証環境。ノードイメージはkindest/node:v1.37.0 | :::note alert Agent Executorはプレビューで、READMEは破壊的変更が入る見込みであることを明記しています。 > We are still actively refining our core concepts, protocols, and specifications. We will likely to introduce major breaking changes prior to a stable release. (参考: README.md#L6-L9) 本記事の実装説明は AX v0.3.1 に固定します。Agent Substrate は、このタグの依存先コミットを確認対象にします。別記事で扱った Substrate v0.5.0 とは API が異なるため、設定例をそのまま混ぜないようにします。リポジトリ名を省略したソースコードの引用は、すべて google/ax の v0.3.1 です。 ::: TL;DR - AX は Task を管理し、実行を Substrate の Actor に任せます。Task・Workspace・Model は独自の gRPC API で扱い、Kubernetes の CRD にはしません。 - 状態は Redis、作業キューは Redis Streams に置きます。イベント単位の分配は、Task ごとの直列化や処理成功の保証とは異なります。 - AX の Suspend は /workspace のデータを保存します。中断時のプロセスメモリやルートファイルシステムの変更は保存せず、再開には golden snapshot と durable data を使います。 - ブログの機能説明と v0.3.1 の実装は分けて読みます。応答の再送や Task の分岐など、コードで確認できない機能もあります。 - 検証には WorkerPool と保存先の設定が必要です。arm64 では runner イメージを用意し、Task の準備完了とコマンドの成否を分けて確認しました。 第1章: Agent Executorとは この章では、ブログが説明しているAgent Executorの機能を確認し、リポジトリのv0.3.1が提供している内容と照らし合わせます。 1.1 ブログの説明 Google Cloud のブログを参考に、機能と構成を整理します。以下の公式図はブログの説明する全体像で、v0.3.1 の実装状況は1.3節で確認します。 ブログは、長時間動くエージェントのワークフローが障害に弱く、本番環境で信頼性と効率を保って管理することが難しい、という課題から説明を始めています。 > Today, we're introducing Agent Executor, Google's open-source runtime standard for agent execution, resumption, and distributed deployment. (参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog) Agent Executorが備える機能として、ブログは次の5つを挙げています。 | 機能 | ブログの説明の要約 | |---|---| | Durable execution | イベントログとスナップショットにより、中断や障害から実行を再開する | | Secure isolation | sandbox により、コードの副作用や悪意ある処理の影響を隔離する | | Session consistency | single-writer により、共有セッション状態への更新を調整する | | Connection recovery | クライアントの再接続時に、最後に受信したシーケンス以降の応答を補完する | | Trajectory branching | checkpoint から実行を分岐し、複数の処理を試す | (参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog) 最初の公式図は、実行を調整する Controller、履歴を扱う Event Log、実行対象の Actors という3つの要素を示しています。Actors には、エージェントだけでなくハーネス、スキル、ツール、sandbox も含まれます。 !Agent Executorの構成図。ControllerとEvent Logが双方向に接続され、Controllerから、Harnesses/Agents/Skills/Tools/Sandboxesを含むActorsへ矢印が伸びている。 (参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog, 2026-05-20) 図中の Event Log と、後述する Redis Streams の作業キューは、同じ機能として扱えません。Streams があることだけで、応答の再送や実行履歴の復元まで実装されているとは判断できません。 2つ目の公式図は、異なるエージェントや実行環境を組み合わせる構想です。Antigravity、自社で運用するエージェント、Google が管理するエージェントなどを、Agent Executor の周囲に配置しています。個々の連携が v0.3.1 に実装済みであることを示す図ではありません。 !Agent Executorを中央に置き、Antigravity(またはcustom harness)、Custom agents managed by Google、Frontier agents from Google、Self-managed agentsと双方向の矢印で接続している図。 (参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog, 2026-05-20) Kubernetes上でのスケールについては、同日に発表されたAgent Substrateと組み合わせると説明されています。 > Agent Substrate introduces a new level of abstraction for Kubernetes that moves agents onto and off of ready compute capacity in real-time, resulting in lower latency with higher scale and efficiency. (参考: Agent Executor, Google's distributed Agent Runtime | Google Cloud Blog) Agent Substrateのリポジトリ側でも、Agent Executorはエコシステムの1つとして紹介されています。 > Agent Executor: A distributed agent runtime that demonstrates building a secure, hyper-scalable agent harness on Agent Substrate (参考: agent-substrate/substrate README.md#L46) 1.2 リポジトリが提供するもの google/axのREADMEは、AXを次のように説明しています。 > AX is a high-throughput, declarative orchestrator to run billions of autonomous agent workloads in a cluster. It runs on top of Agent Substrate for sandboxed execution and is built to run billions of tasks per cluster. If you have used Kubernetes, ax will feel similar. (参考: README.md#L13) README の「billions」はプロジェクトが掲げる規模の目標で、本記事で検証した性能値ではありません。エージェントを「ステートレスなマイクロサービスでも、完了まで走るバッチジョブでもない新しい種類のワークロード」と位置づけ、3つのリソースで宣言的に扱う、という設計です。 > Agents 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. (参考: README.md#L47) | やりたいこと | AXが提供するもの | |---|---| | 信頼できないエージェントのコードを、CPU/メモリ制限付きの隔離されたsandboxで実行する | Task | | Gitリポジトリ、MCPサーバー、skillパッケージをあらかじめ用意して、エージェントがすぐ動ける状態で起動する | Workspace | | プラットフォーム自身が使うLLMを、Kubernetesのsecretの認証情報とともに設定する | Model | | エージェントを一時停止し、保存したデータを使って再開する | ax suspend / ax resume | | 実行中のエージェントにシェルで入って中の様子を見る | ax ssh | (参考: README.md#L49-L55の機能紹介を要約。リソース制限の実装上の注意点は6.2節) 1.3 ブログの機能とv0.3.1のコードの対応 ブログの5つの機能と、v0.3.1のソースコード・同梱ドキュメントを突き合わせると、次のようになります。「見つからなかった」と書いている箇所は、リポジトリ内をevent logやsingle-writerなどで検索した範囲での結果です。 | ブログの機能 | v0.3.1で確認できたこと | |---|---| | Durable execution | スナップショットによるSuspend/Resumeは、Agent SubstrateのActorTemplateのSnapshotsConfigを通じて使っている(4.4節)。イベントログに相当する実装は見つからなかった | | Secure isolation | ActorTemplateのSandboxConfigでgVisorを指定しており、隔離はAgent Substrateのsandboxに任せている(4.4節) | | Session consistency | シングルライターに相当する実装は見つからなかった | | Connection recovery | 最後に受信したシーケンスから応答を補完する実装は見つからなかった。WatchTaskはTaskのステータス遷移を流すRPCで、エージェントの応答を再送するものではない(2.4節) | | Trajectory branching | ロードマップの項目(「Stateful Task Branching」)で、実装はまだない(第7章) | | 他のエージェントとの組み合わせ | Antigravityは、Workspaceのgoalを実行する環境準備のために使われている(5.2節)。LangChain/LangGraph、ADK、A2Aに関する記述や実装は見つからなかった | ブログはプロジェクトの機能を説明していますが、本記事が動作を確認する対象は v0.3.1 です。表の「見つからなかった」は、このタグの公開コードで対応する実装を確認できなかったという意味です。 第2章: 全体アーキテクチャ この章では、AXのコンポーネントと、Taskがax applyされてから実行されるまでのデータの流れを整理します。 2.1 コンポーネント AXは4つのバイナリで構成されています。 | バイナリ | 役割 | |---|---| | ax | 開発者向けのCLI。マニフェストの適用、リソースの参照・watch、クラスタへのトンネルを担う | | ax-server | ステートレスな gRPC API(ポート8080)。CLI が YAML から変換した型付きリクエストを受け、検証・保存とイベント発行を行う | | ax-controller | 再調整を行うworker。Redisのストリームを読み、Agent Substrate上にatespaceとactorを用意して、Taskを望ましい状態に近づける。レプリカを増やしてスケールする | | ax-task-runner | 各Taskのコンテナの中で動くエントリポイント。ワークスペースを準備し、メタデータを提供し、エージェントのコマンドを実行する | (参考: DESIGN.md#L37-L44) v0.3.1 の構成では、AX が Task の管理を、Agent Substrate が Actor の実行を担当します。DESIGN.md を基に、役割の境界が分かるように整理した図です。 flowchart TD CLI["ax CLI<br/>Task / Workspace / Model を適用"] subgraph AX["AX の制御層"] Server["ax-server<br/>gRPC API"] Redis[("Redis<br/>リソースの状態<br/>作業キュー / 状態変更通知")] Controller["ax-controller<br/>Task を再調整"] Server -->|保存・イベント発行| Redis Redis -->|Redis Streams| Controller end subgraph Substrate["Agent Substrate の実行基盤"] API["Control API<br/>Actor の配置・停止・再開"] Actor["Worker 上の Actor<br/>ax-task-runner + エージェント"] API -->|Worker へ割り当て| Actor end CLI -->|gRPC| Server Controller -->|ActorTemplate / Actor の作成など| API (参考: DESIGN.md#L7-L35) 2.2 etcdの外にRedisを置く理由 AXがCRDではなくRedisを使う理由は、DESIGN.mdの冒頭に書かれています。 > Storing millions of short-lived tasks as Kubernetes CRDs pushes etcd past its comfort zone (single-digit GB storage limits, write-rate bottlenecks, control plane degradation). AX keeps its state in Redis and uses Redis Streams as the work queue between the API server and a horizontally scaled pool of controllers. (参考: DESIGN.md#L5) これは、AX が想定する規模に対するプロジェクトの設計判断です。etcd や CRD の一律の上限を示すものではありません。数百万の短命な Task による更新負荷を避けるため、状態をRedisに置き、Redis Streamsをapiサーバーとcontrollerの間の作業キューにする、という設計です。Kubernetes の管理対象から高頻度に変わる状態を分ける点は、Agent Substrate と共通しています。AX は Task を Redis に、依存先の Substrate は Actor の状態を PostgreSQL に保存し、それぞれ異なるリソースを管理します。 RedisのキーはTask・Workspace・Modelごとに作られます。kindで実際にax applyした直後にredis-cli --scanで確認すると、次のキーがありました。 $ redis-cli --scan | sort ax:stream:tasks ax:task:default:simple-task ax:tasks:atespace:default ax:tasks:index ax:task:<atespace>:<name>がTask本体(protojsonでシリアライズした文字列)、ax:tasks:indexとax:tasks:atespace:<atespace>が一覧用のソート済みセットで、ax:stream:tasksがイベントのStreamです。SaveTaskは、これらのキーの更新とStreamへのイベント追加を1つのトランザクションにまとめています。 pipe := s.client.TxPipeline() pipe.Set(ctx, s.taskKey(atespace, name), data, s.opts.TTL) pipe.ZAdd(ctx, s.taskIndexKey(), redis.Z{Score: score, Member: member}) pipe.ZAdd(ctx, s.taskAtespaceIndexKey(atespace), redis.Z{Score: score, Member: name}) pipe.XAdd(ctx, &redis.XAddArgs{ Stream: s.opts.StreamName, Values: map[string]interface{}{ "action": "reconcile", "atespace": atespace, "name": name, }, }) pipe.Publish(ctx, s.taskPubSubChannel(atespace, name), data) (参考: internal/store/redis/store.go#L123-L176) 最後のPublishは、ax watchのためのPub/Subです。 2.3 Redis Streamsのコンシューマーグループ ax-controllerは、Redis Streamsのコンシューマーグループ(デフォルトはax-controllers)に参加して、イベントを受け取ります。 streams, err := sub.store.client.XReadGroup(ctx, &redis.XReadGroupArgs{ Group: sub.group, Consumer: sub.consumer, Streams: []string{sub.store.opts.StreamName, ">"}, Count: sub.store.opts.ReadBatchSize, Block: sub.store.opts.ReadBlock, }).Result() (参考: internal/store/redis/store.go#L631-L637) 通常の新規配信では、同じグループ内の1つのコンシューマーにイベントが割り当てられます。ただし、同じ Task に対する別々のイベントは、異なるコンシューマーで並行して処理される可能性があります。Task ごとのロックや single-writer を、この仕組みだけで保証するわけではありません。既存の検証では ax-controller を2レプリカにし、同じ Task に対して8回 ax apply を実行しました。以下の出力では2つのコンシューマーが登録され、どちらも pending が0であることを確認できます。これは均等配分や exactly-once 処理を示すものではありません。 $ kubectl -n ax-system scale deploy/ax-controller --replicas=2 $ redis-cli XINFO CONSUMERS ax:stream:tasks ax-controllers name ax-controller-79c5679f9f-jj2sg-7357 pending 0 idle 279 name ax-controller-79c5679f9f-r74l6-2741 pending 0 idle 1195 (出力はinactiveを省略しています) worker.goのRunは、processEventが失敗した場合もログに出力したうえで、必ずイベントをACKします。失敗したイベントがキューに残り続けて、後続のイベントの処理を止めることを避けるためです。 if err := w.processEvent(ctx, ev); err != nil { slog.Error("error processing task event", "id", ev.ID, "atespace", ev.Atespace, "name", ev.Name, "action", ev.Action, "error", err, ) } if err := sub.Ack(ctx, ev); err != nil { slog.Warn("failed to acknowledge task event", "id", ev.ID, "error", err) } (参考: internal/controller/worker.go#L92-L103) 失敗したイベントを自動で再投入する処理はありません。すでに別のイベントがあれば処理されますが、それもなければ新しいイベントの発行が必要です。kindでも、失敗したTaskのイベントがACKされた状態(pending 0)のままFailedで止まり、ax resumeやax applyを実行して新しいイベントが発行されるまで、再調整は動きませんでした(8.6節)。 2.4 削除と状態変更の通知 ax deleteは、Taskのレコードをすぐには消しません。MarkTaskDeletingでフェーズをTerminatingにして、削除イベントを発行します。 pipe := s.client.TxPipeline() pipe.Set(ctx, s.taskKey(atespace, name), data, s.opts.TTL) pipe.Publish(ctx, s.taskPubSubChannel(atespace, name), data) pipe.XAdd(ctx, &redis.XAddArgs{ Stream: s.opts.StreamName, Values: map[string]interface{}{ "action": "delete", "atespace": atespace, "name": name, }, }) (参考: internal/store/redis/store.go#L284-L317) ax-controller は、削除イベントを受け取ると Actor を削除し、Task 用 ActorTemplate の削除を試みてから、Redis のレコードを消します。 if err := w.reconciler.ReconcileDelete(ctx, ev.Atespace, ev.Name); err != nil { // Leave the record in Terminating so the failure is visible; re-running // `ax delete` republishes the event and retries the cleanup. return fmt.Errorf("cleaning up task %s/%s: %w", ev.Atespace, ev.Name, err) } if err := w.store.DeleteTask(ctx, ev.Atespace, ev.Name); err != nil { (参考: internal/controller/worker.go#L107-L119) Actor の削除が失敗すると Terminating のまま残り、ax delete を再実行すると再試行できます。一方、ActorTemplate の削除エラーは警告ログに記録するだけで、Task の削除は続行します。Task が一覧から消えたことだけでは、関連テンプレートがすべて削除されたとは限りません。 (参考: reconciler.go#L390-L405) ax watchが使うWatchTaskも、この仕組みの上にあります。RedisのPub/Subを購読して、最初に現在のTaskをINITIALとして送り、以降は変更をMODIFIEDとして流します。更新通知で Task のフェーズが Running・Failed・Completed になるとストリームを閉じます。初回の INITIAL 送信では同じ終了判定を行わないため、すでに Running の Task を watch した場合は次の通知を待つことがあります(参考: internal/server/server.go#L201-L238)。ブログの「Connection recovery」のように、最後に受信した位置から応答を補完する仕組みではありません。 第3章: Task・Workspace・Model この章では、AXが扱う3種類のリソースを、docs/concepts.mdとmanifests.mdをもとに整理します。いずれのリソースも、ax.io/v1alpha1のマニフェストとしてax applyで適用し、Atespace(Agent Substrate 上の論理的なグループと名前の識別範囲)ごとに管理されます。Kubernetes namespace や、認可・隔離の保証とは区別します。 3.1 Task Taskは、隔離された実行の最小単位です。 > The smallest unit of isolated execution. A Task declares the container image and command, compute requests and limits, environment variables, and references to one or more Workspaces under spec.workspaces. Each workspace is mounted at its own path, and the first serves as the command's working directory. (参考: docs/concepts.md#L7) 最小の Task は、イメージと環境変数だけで書けます。以下は同梱例で、8章では検証環境に合わせた runner イメージへ差し替えます。command がない場合は、runner がメタデータを提供しながら待機し、LLM を自動で呼び出すわけではありません。 apiVersion: ax.io/v1alpha1 kind: Task metadata: name: simple-task atespace: default spec: image: "gcr.io/ax-substrate/ate-images/ax-task-runner@sha256:3a0dea6ad8b55278685db58aca6e37dc4ba04056831d45bef3aaeafdca43cac6" env: - name: ENVIRONMENT value: "test" debug: true # serve guest services so `ax ssh` works; off by default (参考: examples/simple.yaml) status.phaseは、Pending・Running・Suspended・Failed・Terminatingなどの1語で、詳細はconditionsに入ります。ax watchで遷移を見られます。 stateDiagram-v2 [*] --> Pending : ax apply Pending --> Running : Resumeに成功 Pending --> Failed : 再調整に失敗 Running --> Suspended : ax suspend Suspended --> Running : ax resume Running --> Failed : 再調整に失敗 Suspended --> Failed : 再調整に失敗 Failed --> Running : ax resumeまたはax apply Pending --> Terminating : ax delete Running --> Terminating : ax delete Suspended --> Terminating : ax delete Failed --> Terminating : ax delete Terminating --> [*] : ActorとActorTemplateを削除後 (参考: 初期値のPendingはstore.go#L142-L144、Failed・Suspended・Runningはreconciler.go#L193-L218、Terminatingはstore.go#L284-L317。FailedからRunningへの遷移は、kindでの確認結果をもとにした図示です) conditionsは2種類です。 | Condition | Trueになる条件 | |---|---| | WorkspaceReady | すべてのworkspaceのセットアップが終わった。以降はTrueのまま | | Ready | Actor が実行中で、controller が WorkspaceReady を True と認識している | (参考: docs/concepts.md#L15-L18) Running や Ready=True は、タスクのコマンドが成功したことや、Git の取得が成功したことを保証しません。5.1節・5.2節で実際の判定内容を説明します。 3.2 Workspace Workspaceは、エージェントが作業を始められる状態にするための準備(Gitリポジトリのclone、MCPサーバー、skill)を、宣言的に1回だけ書いておくためのリソースです。 > A Workspace populates the filesystem and tool landscape. Declare it once, bind it from as many tasks as you like, and the runner materializes it inside each sandbox before the command starts: > > - Git repositories cloned into subdirectories of the workspace path. > - MCP servers and registries (Model Context Protocol) that the agent can call. > - Skill registries and the path where skills are materialized. (参考: docs/concepts.md#L26-L30) Taskとの紐づけにはgoalも指定できます。goalは、環境準備を自然言語で書いたもので、最初の起動時にエージェントへ渡され、ツールチェインや依存関係のインストールなどを任されます(5.2節)。 spec: workspaces: - name: my-service # mounted at /workspace/my-service, the command's working directory goal: "Install dependencies and run the test suite" - name: team-tools path: "/workspace/tools" # explicit mount path (参考: docs/manifests.md#L40-L47) ただし、v0.3.1のrunnerのコードで実際に処理しているのは、Gitのclone、skillsのディレクトリの作成、goalのAntigravityへの引き渡しです(参考: internal/workspace/setup.go#L83-L124)。mcpやskills.registriesの中身は、ax describe workspaceの表示と、Taskの中の/metadata/v1alpha1/ax/workspacesが返すYAMLには含まれますが、runnerがそれをファイルに書き出す処理は見つかりませんでした。 3.3 Model Modelは、LLMそのものではなく、名前を付けたモデルの設定です。 > A Model is not a model. It is a named model configuration: which provider to call, which model identifier to use, provider-specific generation parameters such as temperature, and a reference to the Kubernetes secret holding the API key. (参考: docs/concepts.md#L36) apiVersion: ax.io/v1alpha1 kind: Model metadata: name: default-model atespace: default spec: provider: google model: gemini-3.8-flash secretKey: name: gemini-api-secret key: GEMINI_API_KEY (参考: docs/manifests.md#L89-L101) 同梱ドキュメントは provider に google と anthropic を挙げています(参考: docs/manifests.md#L81-L123)。concepts.mdは、この設定を使う場面として「goalからworkspaceを計画するとき」を挙げていますが、v0.3.1では、Modelを読み込むPlanner(internal/workspace/planner.go)は、定義されているだけで、controllerやrunnerからは呼ばれていません。goalの環境準備に実際に使われるAPIキーは、Model リソースではなく、Atespace と同名の Kubernetes namespace にある gemini-api-secret の GEMINI_API_KEY です。取得できない場合は controller 自身の GEMINI_API_KEY 環境変数へフォールバックします。取得した値は Task のコンテナの環境変数へ渡されます(参考: reconciler.go#L352-L368)。 第4章: TaskがActorになるまで この章では、ax-controllerが1つのTaskを再調整する流れを、reconciler.goの実装に沿って整理します。 4.1 再調整の流れ ax-controllerのReconcileは、Taskを受け取って、Agent SubstrateのControl APIを次の順に呼びます。 sequenceDiagram participant W as ax-controller participant S as Agent Substrate<br/>Control API participant A as Actor<br/>(ax-task-runner) W->>S: EnsureAtespace W->>S: ActorTemplateを作成(Task単位) W->>S: EnsureActor(名前はTaskと同じ) alt spec.suspend が true W->>S: SuspendActor Note over W: phase = Suspended else W->>S: ResumeActor S-->>W: workerのIP loop WorkspaceReadyになるまで(最大15秒) W->>A: GET /readyz?check=workspace end Note over W: phase = Running end (参考: internal/controller/reconciler.go#L95-L302) 1. atespaceの作成: EnsureAtespaceで、Taskが属するatespaceをSubstrate側に用意します(参考: reconciler.go#L119-L124)。 2. Actorの名前: Actorの名前は、常にTaskの名前と同じです。atenet-routerのate-target-actorヘッダーにも、そのまま使えます。 // 2. The actor is always named after the task, so the two can be used // interchangeably (for example in the router's ate-target-actor header). actorName := task.Metadata.Name task.Status.Actor = actorName (参考: reconciler.go#L126-L130) 3. ActorTemplate: Taskごとに専用のActorTemplateを作ります(4.2節)。 4. Actor: EnsureActorでActorを作ります。すでに存在し、状態がCRASHEDなら削除して作り直します(参考: internal/substrate/client.go#L306-L366)。 5. Suspend/Resume: spec.suspendがtrueならSuspendActor、そうでなければResumeActorを呼びます。ResumeActorのレスポンスに含まれるworkerのIPが、ax get tasksのWORKER-IP列になります。 6. readyzの確認: Resumeの後、Actorの中のrunnerの/readyz?check=workspaceを500msごとに確認します。ワークスペースのセットアップは1回しか行われないため、WorkspaceReadyが一度Trueになった後は、このポーリングは行いません(参考: reconciler.go#L220-L291)。 Substrateの認証には、ax-controllerのPodに投影されたServiceAccountのトークン(audienceはapi.ate-system.svc)を使い、SubstrateのサーバーのTLS証明書は、servicedns.podcert.ate.dev/identityというsignerのClusterTrustBundleで検証します(参考: deploy/ax-controller.yaml#L69-L119)。このsignerは、Agent Substrateの概要で整理したpodcertcontrollerが提供しているものです。 4.2 Taskごとに作られるActorTemplate ActorTemplateは、Taskごとに作られます。名前は、Taskの名前と、イメージ・環境変数のダイジェストから決まります。 // taskTemplateName derives the per-task ActorTemplate name from the task name and a // digest of the image and container environment, so a spec change yields a new template. func taskTemplateName(taskName, image string, env map[string]string) string { (参考: reconciler.go#L371-L385) 環境変数には、ユーザーがspec.envに書いたものに加えて、Taskの起動設定(AX_TASK_YAML)とバインドしたWorkspaceのYAML(AX_WORKSPACES_YAML)も含まれます。ただし、statusとsuspendのフラグは除かれています。suspendを切り替えるたびに新しいゴールデンスナップショットが作られることを避けるためです。 // Only launch configuration belongs in the template; status and suspend // changes must not create new golden snapshots. launchTask := proto.Clone(task).(*v1alpha1.Task) launchTask.Status = nil launchTask.Spec.Suspend = false if taskYAML, err := yaml.Marshal(launchTask); err == nil { extraEnv["AX_TASK_YAML"] = string(taskYAML) } (参考: reconciler.go#L157-L169) 起動設定を変更すると、新しい名前の ActorTemplate が作られます。ただし、EnsureActor は既存 Actor が正常ならその Actor を返し、参照するテンプレートを更新しません。新しい ActorTemplate の作成と、既存 Actor への設定反映は別です。ax apply を Deployment のローリングアップデートと同じ動作として扱うことはできません。 (参考: client.go#L306-L365) kindで2つのTaskを作ったときは、simple-task-tmpl-5200c4f4のような名前のテンプレートが、Taskごとに1つずつ作られました。削除時は、名前のパターンに一致するテンプレートをすべて消します(参考: reconciler.go#L409-L445)。 4.3 ActorTemplateの中身 ActorTemplateは、substrate.BuildActorTemplateが組み立てます。 Containers: []*ateapipb.Container{{ Name: "guest", Image: image, Command: command, Env: envList, Readyz: &ateapipb.ContainerReadyz{ HttpGet: &ateapipb.HTTPGetAction{ Path: "/readyz", Port: 80, }, }, VolumeMounts: []*ateapipb.VolumeMount{{ Name: "workspace", MountPath: "/workspace", }}, }}, Volumes: []*ateapipb.Volume{{ Name: "workspace", DurableDir: &ateapipb.DurableDirVolumeSource{}, }}, (参考: internal/substrate/client.go#L212-L262) コンテナのコマンドは常に/usr/local/bin/ax-task-runnerで、/workspaceはdurableDirのボリュームとしてマウントされます。ユーザーがspec.commandに書いたコマンドは、コンテナのエントリポイントにはならず、AX_TASK_YAML経由でrunnerが受け取って実行します(5.1節)。 4.4 スナップショットとsandboxの設定 スナップショットとsandboxの設定は、次のようにコードに固定されています。 SnapshotsConfig: &ateapipb.SnapshotsConfig{ StorageLocation: snapshotsBucket, OnPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA, OnCommit: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA, OnResume: &ateapipb.OnResumeConfig{ FromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN, }, }, SandboxConfig: &ateapipb.SandboxConfig{ SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR, ConfigName: "gvisor-default", }, (参考: internal/substrate/client.go#L263-L274) Agent Substrateのprotoのコメントには、SNAPSHOT_CONTENT_SCOPE_DATAとRESUME_SOURCE_GOLDENが、次のように説明されています。 > Captures durable data without process memory or root filesystem changes. (参考: agent-substrate/substrate ateapi.proto#L194-L195) > Restores with the version's golden snapshot and the actor's own durable data. (参考: agent-substrate/substrate ateapi.proto#L889-L890) つまり、Suspend時に保存されるのは、プロセスのメモリやルートファイルシステムの変更を除いた、durableなデータ(/workspace)だけです。Resume時は、そのバージョン(ActorTemplate)のゴールデンスナップショットと、Actor自身のdurableなデータから復元されます。 保存するデータと、復元の土台を分けると次のようになります。 flowchart LR subgraph Before["Suspend 前の Actor"] Data["/workspace<br/>作業ファイル"] Volatile["実行中のメモリ<br/>/tmp などの変更"] end Saved[("Actor 自身の<br/>durable data")] Golden[("ActorTemplate の<br/>golden snapshot")] NotSaved["中断時の状態として<br/>保存しない"] Restored["Resume 後の Actor<br/>golden snapshot を土台に<br/>/workspace のデータを復元"] Data -->|Suspend: DATA| Saved Volatile -.-> NotSaved Saved --> Restored Golden -->|Resume: GOLDEN| Restored AX v0.3.1 の固定設定を簡略化した図。中断直前のプロセスメモリを保存する方式とは異なります。 (参考: client.go#L249-L274、ateapi.proto#L889-L890) Agent Substrateの概要で動かしたCounterデモは、SNAPSHOT_FIDELITY_MEMORYでプロセスのメモリごと保存していました。AXは、メモリではなく/workspaceのデータだけを保存する設定を選んでいます。これはdocs/runner.mdにも書かれています。 > The /workspace volume is what survives suspend and resume. Agent Substrate snapshots it when a task is suspended and restores it into a fresh container when the task is resumed, so the runner will see the same files but a new process tree. (参考: docs/runner.md#L26) スナップショットの保存先は、AX_SNAPSHOTS_BUCKET環境変数で指定します。未指定のときのデフォルト値は、コード内に固定されたバケットのURIです(参考: internal/substrate/client.go#L205-L228)。deploy/ax-controller.yamlも、同じバケットをAX_SNAPSHOTS_BUCKETに設定しているため(参考: deploy/ax-controller.yaml#L82-L83)、自分の環境にデプロイするときは、この値を差し替える必要があります(8.1節)。 第5章: Taskのコンテナの中 この章では、ActorとしてSubstrate上で動くコンテナの中で、ax-task-runnerが何をしているかを整理します。 5.1 runnerの契約 ax-task-runnerは、各Taskのコンテナで、PID 1として動くプログラムです。コントローラーは、spec.commandをコンテナのエントリポイントとして実行せず、常にrunnerを起動し、設定を環境変数で渡します。 | コントローラーが設定するもの | 値 | |---|---| | コンテナのコマンド | 常に/usr/local/bin/ax-task-runner | | AX_TASK_YAML | Taskの起動設定(statusとsuspendのフラグを除く)のYAML | | AX_WORKSPACES_YAML | バインドしたWorkspaceの、複数ドキュメントのYAML | | /workspace | durableなディレクトリとしてマウント | | readiness probe | ポート80のGET /readyz | (参考: docs/runner.md#L13-L22) runnerには、次の動作が求められます。 - ポート80でHTTPを提供し、/healthzと/readyz(ワークスペースの準備ができるまで503)、/metadata/v1alpha1/ax/taskと/metadata/v1alpha1/ax/workspacesを返す - 各workspaceを1回だけ準備する(再起動のたびに再準備しない) - spec.commandを、最初のworkspaceを作業ディレクトリにして子プロセスとして実行し、監視する - コマンドが終了しても動き続ける(PID 1が終了するとメタデータサーバーも止まり、ax sshが使えなくなるため) - SIGTERMを受けたらコマンドのプロセスグループに転送し、猶予期間の後にSIGKILLする - spec.debugがtrueのときだけ、ゲストサービスを提供する (参考: docs/runner.md#L28-L47) コマンドの終了後もrunnerが動き続ける点は、コードにも現れています。 select { case err := <-exited: reportExit(cfg, cmd, err) // Keep the sandbox up and inspectable until told to stop. <-ctx.Done() (参考: runner/runner.go#L190-L196) docs/runner.mdには、「コマンドの終了ステータスをコントロールプレーンは現状読み取らない」とも書かれています(参考: docs/runner.md#L43)。Taskのフェーズは、コマンドが成功したかどうかとは別に決まります。 runnerは、同梱のデフォルト実装を使わずに置き換えることもできます。イメージを継承する、runnerパッケージを自分のバイナリに埋め込む、まったく別の言語で書く、の3段階があり、いずれも/usr/local/bin/ax-task-runnerに実行ファイルを置く契約を満たす必要があります(参考: docs/runner.md#L58-L134)。 5.2 ワークスペースの準備 ワークスペースの準備は、各workspaceのパスごとに、最初の1回だけ実行されます。Gitリポジトリのclone(失敗時は5回までリトライ)、skillsのディレクトリの作成、goalが指定されていればAntigravityへの引き渡しの順です。完了すると、/axの下にマーカーファイルを書き、以降の起動ではスキップします。 markerPath := filepath.Join(AXDir, MarkerName(targetPath)) if _, err := os.Stat(markerPath); err == nil { slog.Info("workspace already initialized; skipping maiden run setup", "path", targetPath) return res, nil } (参考: internal/workspace/setup.go#L83-L124) goalの引き渡しは、antigravity_bootstrap.pyというスクリプトに対して、GEMINI_API_KEYが環境変数にある場合に、10分(AX_BOOTSTRAP_TIMEOUTで変更可)の制限時間で実行します。スクリプトかGEMINI_API_KEYがなければ、ログを出してこのステップをスキップします。 cmd := exec.CommandContext(ctx, "python3", bootstrapScriptPath, "--goal", goal, "--workspace", targetPath, "--data-dir", dataDir, ) (参考: internal/workspace/setup.go#L277-L315) Gitのcloneに失敗しても、セットアップ全体は失敗扱いになりません。失敗はログとして/ax/git-error.logに残り、マーカーファイルだけが書かれません。コードのコメントは、これによって次の起動で再試行されることを意図しています。ただし、kindで試した限りでは、Resumeしてもcloneは再試行されませんでした(8.5節)。runner に伝わるセットアップエラーは、作業ディレクトリの作成失敗などです。この結果、Git の取得失敗が WorkspaceReady に反映されない場合があります。8.5節で、kindでこの動作を確認しています。 5.3 ax sshの仕組み ax sshは、SSHではなく、Agent Substrateのゲストサービス(agent-substrate/env)のgRPCを使います。spec.debug: trueのときだけ、runnerが同じポート80で、HTTPとh2cを多重化してこのサービスを提供します。 > - Process service: start, inspect, stream output from, and kill processes. This is what powers ax ssh. > - File system service: streaming file reads and writes inside the workspace. (参考: docs/sandbox.md#L32-L33) CLIは、kubectl port-forwardでatenet-routerへ接続し、ate-target-actorヘッダーにTaskの名前を入れて、プロセスの開始と出力のストリームを呼び出します(参考: cmd/ax/main.go#L1084-L1094、internal/guest/client.go#L45-L73)。spec.debugがfalseのTaskには、CLIの側で接続を拒否します(参考: cmd/ax/main.go#L1045-L1051)。 ゲストサービスは、任意のプロセス実行とファイルアクセスを許すため、デフォルトでは無効です。 5.4 ネットワーク Taskには、KubernetesのServiceやIngressはありません。通常のクライアントから Task へ接続する方法として、Agent Substrate の atenet-router(ate-system名前空間のatenet-routerサービス)を使い、ate-target-actor: <atespace>/<task> ヘッダーで宛先を指定します。controller の準備完了チェックには worker IP への直接接続もあります。宛先のActorが停止中なら、先に Resume してからリクエストを転送します。これは AX の spec.suspend や Task status を更新する操作ではないため、Actor と Task の表示が一致するとは限りません(参考: docs/networking.md#L3-L5)。 第6章: 構築にあたって この章では、AXをデプロイするために必要なものと、v0.3.1の時点で注意しておきたい点を整理します。 6.1 前提となるもの READMEは、AXを動かす前提として、Agent Substrateがクラスタで動いていることを挙げています。 > AX schedules every task as a sandboxed actor on Agent Substrate, so Substrate must be running in your cluster before you deploy AX. (参考: README.md#L63) 実際にkindで動かしたところ、次のものを別途用意する必要がありました。 | 追加で用意するもの | 役割 | 備考 | |---|---|---| | Agent Substrate | Actorの実行基盤 | AX v0.3.1のgo.modが固定するコミットを使った(Agent Substrate v0.5.0との組み合わせは確認していない) | | WorkerPool | Actorを載せるworkerの集まり | AXのリポジトリにはWorkerPoolの記述がなく、デプロイ手順にも含まれない。無いとno free workers availableでResumeに失敗する | | スナップショットの保存先 | Actorのスナップショットの保存先 | AX_SNAPSHOTS_BUCKETで差し替える。デフォルトはコード内に固定された値 | | Taskのruntimeイメージ | /usr/local/bin/ax-task-runnerを含むイメージ | デフォルトのイメージはgcr.io/ax-substrate/...から取得される。make build-task-runnerはlinux/amd64向けにビルドするため(参考: Makefile#L46-L51)、arm64の環境では自分でビルドする必要があった | | Redis | Task・Workspace・Modelの状態とイベント | deploy/redis.yamlで用意できる。レプリカ1、永続ボリュームなし | (参考: Redisはdeploy/redis.yaml。WorkerPoolが記述に無いことは、リポジトリ内をWorkerPoolで検索した結果) 6.2 v0.3.1で注意しておきたい点 - Task のリソース設定と、実際の制限を分けて確認します。spec.resources は API に定義されていますが、このタグの BuildActorTemplate はその値を ActorTemplate のリソース設定へ渡していません。WorkerPool の Pod 制限が、そのまま Task ごとの指定値になるわけではありません。(参考: ax.proto#L71-L111、client.go#L212-L275) - Streams の未 ACK イベントを回収する処理は確認できませんでした。新しいコンシューマーグループは $ から作られ、読み取りは > の新規イベントだけを対象にします。初回起動前のイベントや、コンシューマー停止で pending に残ったイベントを自動復旧できるとは限りません。(参考: store.go#L585-L659) - WorkspaceReady の待機には上限があります。ポーリングが時間切れになっても phase は Running のまま、Ready は False になります。後から runner の準備が終わっても、そのことだけで再調整を起こす処理はありません。(参考: reconciler.go#L220-L302) - ax-server に認証処理がありません。grpc.NewServer()にオプションを渡しておらず、平文のHTTP/2(h2c)でポート8080を待ち受けます(参考: internal/server/server.go#L38-L46、cmd/ax-server/main.go#L66-L74)。ax CLIはデフォルトでkubectl port-forwardを使ってax-system名前空間のax-serverサービスへ接続するため(参考: internal/tunnel/tunnel.go#L181-L200)、CLI経由のアクセスは、そのport-forwardを許可するKubernetesのRBACに依存します。クラスタ内の他のPodからax-serverへ直接到達できるかどうかは、NetworkPolicyなどクラスタ側の設定次第です。認証・ガバナンスは、ロードマップ(第7章)の項目に入っています。 - Redisは単一レプリカで、永続ボリュームを持ちません。 deploy/redis.yamlはレプリカ1のDeploymentで、ボリュームがありません。確認したRedisの設定はappendonly no、save 3600 1 300 100 60 10000(デフォルトのRDBスナップショットのみ)でした。Redis Podが再作成されると、Task・Workspace・Modelの状態が失われる可能性があります。Redisをどう構成するかは、利用者が決める前提のようです。 - 失敗したイベントの自動再投入はありません。2.3節で見た通り、失敗時も ACK を試みます。別のイベントがキューにない場合は、ax resume や ax apply などで新しいイベントを発行する必要があります。 - controller は1つのイベントを同期的に処理します。Worker.Runのループは、イベントを1つ取り出し、processEventが終わってから次を読みます。ResumeActorとWorkspaceReadyのポーリング(最大15秒)がこのループの中で行われるため、スケールはレプリカを増やすことで行います。 - Task の status.pendingApproval と status.usage は、このタグで利用する処理を確認できませんでした。 spec.policies(予算・承認の設定)は、過去のバージョンから削除済み(reserved)です(参考: pkg/apis/v1alpha1/ax.proto#L85-L87)。 第7章: ロードマップ この章では、docs/roadmap.mdに書かれている今後の予定のうち、ブログの機能に関係するものを整理します。 ロードマップは5つの項目に分かれています(参考: docs/roadmap.md)。 | 項目 | 内容 | |---|---| | Stabilize Core Specs | Task・Workspace・Model・Sandbox/SandboxConfigのスキーマとライフサイクルの安定化。Taskのトークン/タイムアウトの予算や承認ポリシーも含む | | Actor Architecture | 新しいAgent SubstrateのActor APIへの移行、ワークスペースのセットアップを別Actorに分離、最小権限のポリシー、アイドル検出と自動Suspend、Taskの分岐 | | Agentic Environment | goalによるワークスペースの動的な環境構築、組み込みのブートストラップとエージェントハーネスのカスタマイズ | | Networking, Identity, Governance & Observability | TaskへのSPIFFE ID、MCPとskill registryのガバナンス、OpenTelemetryによるテレメトリと軌跡の収集 | | Documentation | Substrateとの役割分担、カスタムイメージ、カスタムrunnerのドキュメント | ブログが挙げる機能との関係では、次の項目が対応します。 > - Idleness Detection and Automatic Suspension for Density: Continuously monitor actor activity (process execution, I/O, network traffic, and active gRPC/SSH sessions) to detect idle tasks and automatically trigger SuspendActor checkpointing, reclaiming worker CPU and memory to maximize cluster density. > - Stateful Task Branching: Enable branching (forking) a running or suspended Task—including its checkpointed memory and workspace filesystem state—into multiple parallel tasks to explore speculative execution paths concurrently. (参考: docs/roadmap.md#L22-L23) ブログの「Trajectory branching」は、ロードマップの「Stateful Task Branching」にあたります。v0.3.1 はアイドルを自動検出して Suspend する実装ではありません。CLI や上位システムからの明示的な指示が必要です。「Migration to the New Actor」は、AXがAgent Substrateの新しいActor APIへ移行する予定を示しています。今回の検証では、go.modが固定するコミットのAgent Substrateを使いました。 第8章: kindクラスタで実際に動かす この章では、既存の kind 検証記録に、再現に必要な準備と待機手順を補って整理します。arm64 環境で Task の起動・停止、保存されるデータ、失敗時の状態を確認したものです。LLM を使う goal の実行や大規模な負荷試験は対象外です。 8.1 構築 AX v0.3.1 の go.mod は Go 1.27.1 を指定しています。Docker・kubectl・ko と、この Go バージョンを使える環境を用意します。以下は arm64 用の手順です。 Agent Substrate は依存先コミットで構築します。既存の checkout を変更せずに済むよう専用ディレクトリを作ります。クラスタ作成スクリプトは同名の既存クラスタを削除するため、ax-demo が既存環境と重複しないことを確認します。 mkdir ax-kind-review cd ax-kind-review git clone https://github.com/agent-substrate/substrate.git substrate-pinned cd substrate-pinned git checkout --detach 672533541dbf export KIND_CLUSTER_NAME=ax-demo hack/create-kind-cluster.sh hack/install-ate-kind.sh --deploy-ate-system 続いて、AXのRedis、ax-controller、ax-serverをkoでビルドしてデプロイします。deploy/ax-controller.yamlが設定しているAX_SNAPSHOTS_BUCKETは、kindのRustFSのバケット(ate-snapshots)へ差し替えます。 cd .. git clone --branch v0.3.1 --depth 1 https://github.com/google/ax.git ax-v0.3.1 cd ax-v0.3.1 go install ./cmd/ax # Go のインストール先 bin ディレクトリを PATH に追加しておく kubectl apply -f deploy/redis.yaml export KO_DOCKER_REPO=localhost:5001 KO_DEFAULTPLATFORMS=linux/arm64 ko resolve -f deploy/ax-controller.yaml \ | sed 's|gs://dberkov-gke-dev3/ate-env/|gs://ate-snapshots/ax/|' \ | kubectl apply -f - ko resolve -f deploy/ax-server.yaml | kubectl apply -f - kubectl -n ax-system rollout status deploy/ax-redis --timeout=5m kubectl -n ax-system rollout status deploy/ax-controller --timeout=5m kubectl -n ax-system rollout status deploy/ax-server --timeout=5m kubectl -n ax-system get pods 以下は既存の実行結果の抜粋です。Pod 名は実行ごとに変わります。 NAME READY STATUS RESTARTS AGE ax-controller-79c5679f9f-jj2sg 1/1 Running 0 6s ax-redis-86df786c87-sj8z2 1/1 Running 0 30s ax-server-7f8fc9b657-6rdnr 1/1 Running 0 6s Taskのruntimeイメージは、デフォルトのイメージ(gcr.io/ax-substrate/...)を使わず、追加する AX の実行ファイルを /usr/local/bin/ax-task-runner だけにしたイメージを自分でビルドしました。ax-task-runnerをlinux/arm64向けにビルドし、gitを含むベースイメージにコピーして、kindのローカルレジストリにpushします。 CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o ax-task-runner ./cmd/ax-task-runner cat > Dockerfile.kind <<'EOF' FROM alpine/git:latest COPY ax-task-runner /usr/local/bin/ax-task-runner ENTRYPOINT ["/usr/local/bin/ax-task-runner"] EOF docker build --platform linux/arm64 -f Dockerfile.kind -t localhost:5001/ax-task-runner:kind . docker push localhost:5001/ax-task-runner:kind RUNNER_IMAGE=$(docker image inspect localhost:5001/ax-task-runner:kind --format '{{index .RepoDigests 0}}') RUNNER_IMAGE には完全な digest 付きの参照が入ります。続けて、検証用の simple.yaml を作ります。 cat > simple.yaml <<EOF apiVersion: ax.io/v1alpha1 kind: Task metadata: name: simple-task atespace: default spec: image: "${RUNNER_IMAGE}" env: - name: ENVIRONMENT value: test debug: true EOF :::note info このイメージには、デフォルトのイメージに入っているPythonとAntigravity(google-antigravity)が含まれていません。そのため、goalを指定したワークスペースのブートストラップ(5.2節)は、今回の検証の対象外です。 ::: 最初の Task を適用する前に、次の出力に ax-controllers グループがあることを確認します。グループは controller が起動時に作るため、未作成なら controller のログを確認して待ちます。 kubectl -n ax-system exec deploy/ax-redis -- redis-cli XINFO GROUPS ax:stream:tasks AX の CLI は、通常は現在の kubeconfig context から ax-server へのトンネルを自動作成します。以降は kind-ax-demo を対象とし、別の接続先を指定する AX_SERVER や --server を使わない前提です。 8.2 WorkerPoolが無いとResumeに失敗する デプロイ直後に、READMEのとおりax applyでTaskを適用すると、TaskはFailedになりました。 $ ax apply -f simple.yaml # image は上でpushした digest 指定のイメージ task.ax.io/simple-task created $ ax describe task simple-task Conditions: TYPE STATUS REASON MESSAGE Ready False ActorResumeFailed resuming actor default/simple-task: rpc error: code = ResourceExhausted desc = no free workers available ActorTemplateとActorの作成には成功し、ResumeActorの段階で、Actorを載せるworkerがない、というエラーになっています。Agent Substrateでは、ActorはWorkerPoolのworkerに載せられます。kind向けのインストーラが用意するのはシステムコンポーネントまでで、WorkerPoolは含まれていません。AXのリポジトリにも、WorkerPoolを作る手順はありません。以下を ax-workers.yaml として AX 側のディレクトリに保存します。nodeSelector のバージョン値は当時の記録です。再現時は kubectl get nodes -L ate.dev/substrate-version で、インストーラが付けた値と一致するか確認します。 apiVersion: v1 kind: Namespace metadata: name: ax-workers --- apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: ax-workers namespace: ax-workers spec: replicas: 2 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor template: nodeSelector: ate.dev/substrate-version: "67253354" resources: limits: cpu: "1" memory: 1Gi requests: cpu: 250m memory: 1Gi (参考: demos/counter/counter.yaml.tmplをもとに、名前とreplicasを変えたもの。workerImageのko://は、ko resolveでイメージに置き換えて適用する) ko:// の参照は、その Go パッケージを含む Substrate 側の checkout で解決します。 cd ../substrate-pinned ko resolve -f ../ax-v0.3.1/ax-workers.yaml | kubectl apply -f - kubectl -n ax-workers get pods -w 2つの worker Pod が Ready になったら Ctrl-C で監視を終了し、AX のディレクトリへ戻ります。 cd ../ax-v0.3.1 ax apply -f simple.yaml ax get tasks ax describe task simple-task WorkerPool を作った後、ax apply をもう一度実行した既存の記録では、TaskがRunningになり、ReadyがTrueになりました。 $ ax get tasks NAME ATESPACE PHASE ACTOR WORKER-IP AGE simple-task default Running simple-task 10.244.0.24 2m $ ax describe task simple-task Conditions: TYPE STATUS REASON MESSAGE WorkspaceReady True SetupComplete Workspace setup completed at 10.244.0.24 Ready True TaskRunning Task is running and its workspace is ready ax sshで中に入ると、PID 1がax-task-runnerで、spec.envの値とAX_METADATA_URLが設定されていることを確認できました。 $ ax ssh simple-task -- sh -c 'echo $ENVIRONMENT; echo $AX_METADATA_URL; ps -o pid,args | head' test http://127.0.0.1:80 PID COMMAND 1 /usr/local/bin/ax-task-runner 11 sh -c echo $ENVIRONMENT; echo $AX_METADATA_URL; ps -o pid,args | head 12 ps -o pid,args 13 head 8.3 Redisの状態 Redis の内容は、たとえば kubectl -n ax-system exec deploy/ax-redis -- redis-cli XLEN ax:stream:tasks で参照できます。以下の redis-cli コマンドは、その Redis コンテナ内での実行を省略して表記しています。 ax apply を3回実行した時点の既存の記録では、次の状態でした。追加で適用すると件数は変わります。ax applyのたびに、内容が変わらなくても、Streamにイベントが1件追加されます。コンシューマーグループのpendingは0です。 $ redis-cli XLEN ax:stream:tasks 3 $ redis-cli XINFO GROUPS ax:stream:tasks name ax-controllers consumers 1 pending 0 last-delivered-id 1792367750651-0 entries-read 3 lag 0 $ redis-cli TTL ax:task:default:simple-task -1 最初の2回はFailedで終わっていますが、pending 0なので、失敗したイベントもACKされています(2.3節)。TTL は設定されておらず(-1)、期限による自動削除はされません。ただし、Redis 自体の再作成によるデータ消失とは別です。 8.4 Suspend/Resumeで残るもの ax suspend / ax resume の返却は API の受付を示します。実際の状態は ax get tasks と ax describe task で確認し、Suspended への遷移後に Resume、Ready=True の確認後に接続します。ax watch は Ready=False の Running 通知でも終了する場合があるため、終了だけを準備完了と判断しません。 Suspend/Resumeで何が残るかを確認します。/workspace と /tmp にファイルを書き、バックグラウンドで sleep を起動してから、ax suspend と ax resume を実行した記録です。このときはバックグラウンドプロセスが標準出力を保持したため、ax ssh の終了時に stream terminated by RST_STREAM が出ました。再現時はプロセスの標準入出力をリダイレクトすると、出力ストリームの保持を避けられます。 $ ax ssh simple-task -- sh -c 'echo "before suspend" > /workspace/note.txt; echo "volatile" > /tmp/volatile.txt; (sleep 3000 &)' $ ax suspend task simple-task task.ax.io/simple-task suspended $ ax get tasks NAME ATESPACE PHASE ACTOR WORKER-IP AGE simple-task default Suspended simple-task <none> 4m $ ax resume task simple-task task.ax.io/simple-task resumed $ ax ssh simple-task -- sh -c 'ls /workspace; cat /workspace/note.txt; ls /tmp; ps -o pid,args' note.txt before suspend PID COMMAND 1 /usr/local/bin/ax-task-runner 9 ps -o pid,args /workspace/note.txtだけが残り、/tmp/volatile.txtとsleepプロセスは消えました。SuspendでWORKER-IPが<none>になり、Resumeではworkerが変わることもあります(この例では10.244.0.24から10.244.0.23へ変わりました)。これは、/workspaceのデータだけを保存して復元する設定(4.4節)の動作どおりです。Agent SubstrateのCounterデモのように、プロセスのメモリが引き継がれる動作ではありません。 8.5 Git の取得失敗と準備完了判定 Workspace で Git リポジトリを指定した Task も試しました。以下の image は、8.1節で取得した完全な参照へ置き換えます。 apiVersion: ax.io/v1alpha1 kind: Workspace metadata: name: demo-ws atespace: default spec: git: - name: origin repo: "https://github.com/chalk/chalk.git" branch: "main" depth: 1 --- apiVersion: ax.io/v1alpha1 kind: Task metadata: name: ws-task atespace: default spec: image: "<8.1節のRUNNER_IMAGEの値に置き換える>" workspaces: - name: demo-ws debug: true Taskは数秒でRunningになり、ReadyもTrueになりましたが、中を見るとcloneは失敗していました。 $ ax ssh ws-task -- sh -c 'ls /workspace/demo-ws; ls /ax; cat /ax/git-error.log' chalk git-error.log error: git fetch: exit status 128 output: Reinitialized existing Git repository in /workspace/demo-ws/chalk/.git/ error: remote origin already exists. fatal: unable to access 'https://github.com/chalk/chalk.git/': Send failure: Broken pipe 5.2節で見た通り、Gitのエラーはセットアップ全体の失敗として扱われないため、WorkspaceReady: True / SetupCompleteと表示されながら、リポジトリは空のままでした。ログの Broken pipe だけでは、失敗原因を確定できません。egress 設定やプロキシ、接続先などの切り分けは未実施です。AXのリポジトリには、egressに関する記述がロードマップの1行(「setup egress」)しかなく(参考: docs/roadmap.md#L21)、egressポリシーを設定する手順はありません。外部のGitリポジトリをcloneするには、Agent SubstrateのEgressPolicyを別途設定する必要があると思います(今回は検証していません)。 cloneが失敗したあとにResumeしても再試行されるかを、Actorの中に作るローカルのGitリポジトリで確かめました。最初は存在しないパス(/workspace/seed.git)をWorkspaceのリポジトリに指定してTaskを起動し(cloneは失敗します)、起動後にActorの中でそのパスにbareリポジトリを作ってコミットを1つ入れてから、ax suspendとax resumeを実行しました。 $ ax ssh ws2 -- sh -c 'git init -q --bare -b main /workspace/seed.git; ...' # コミットv1を入れる $ ax suspend task ws2 # ax get tasks で Suspended を確認してから再開する $ ax resume task ws2 $ ax ssh ws2 -- sh -c 'ls -a /workspace/demo2/src; git -C /workspace/demo2/src log --oneline' . .. .git fatal: your current branch 'master' does not have any commits yet リポジトリが存在する状態でResumeしても、/workspace/demo2/srcは空のままで、/ax/git-error.logのタイムスタンプも最初の起動のときから変わっていませんでした。手動でgit fetch origin mainを実行すると取得できたため、リポジトリ自体は有効です。この観測では、Resume による Git 取得の再試行は確認できませんでした。Agent Substrateのprotoのコメントによると、RESUME_SOURCE_GOLDENの復元は、ActorTemplateのゴールデンスナップショットとActor自身のdurableなデータから行われます(4.4節)。runnerのプロセスは、ゴールデンスナップショットから復元されている可能性があります(今回は、それ以上の確認はしていません)。 8.6 失敗とリトライ kindでは、次の2つの失敗を確認しました。いずれも自動では再試行されず、ax resumeなどで新しいイベントを発行するまでFailedのままでした。 - workerの不足: workerが2つで、2つのTaskが実行中のときに3つ目のTaskを作ると、no free workers availableでFailedになりました。WorkerPoolのreplicasを4に増やしてからax resumeすると、Runningになりました。 - ゴールデンスナップショットの未完成: Task を作ってまもなく Suspend / Resume を実行すると、FailedPrecondition desc = a Golden data resume requires the ActorTemplate golden snapshot, which is not availableでFailedになりました。しばらく待ってからax resumeをやり直すと、Runningになりました。ActorTemplateのゴールデンスナップショットは、Taskの作成後に非同期で作られるため、その完成前にResumeすると失敗する、と考えられます(4.4節のprotoのコメントに合わせた解釈で、作成のタイミングそのものは確認していません)。 $ ax describe task ws-task Conditions: TYPE STATUS REASON MESSAGE WorkspaceReady True SetupComplete Workspace setup completed at 10.244.0.24 Ready False ActorResumeFailed resuming actor default/ws-task: rpc error: code = FailedPrecondition desc = a
An Overview of Agent Substrate A Kubernetes Pod is a useful unit of deployment. An agent session has a different lifetime: it may run briefly, wait for minutes, then need its process and filesystem state back for the next turn. Keeping a dedicated environment resident through every wait can consume capacity that other sessions could use. Agent Substrate separates those lifetimes. Kubernetes runs a pool of worker Pods; Substrate places sandboxed agent environments onto them, persists their state, and restores them when work arrives. This article follows one request through that system, then examines the components and a Counter demo you can try locally. TL;DR - Kubernetes manages Workers; Substrate manages Actors. Actor state and assignments live behind a separate API backed by PostgreSQL. - Workers can serve many Actors over time. Capacity-based concurrency is combined with suspend/resume to reuse compute. - atelet and ateom save and restore sandbox state. They use gVisor or microVMs; the snapshot fidelity determines what is preserved. - Credentials can stay outside the Actor. An egress gateway and credential provider can inject API credentials into outgoing requests. - Check the implemented behavior, not just the design. Authorization enforcement is experimental and disabled by default for ordinary Control RPCs. Idle detection and performance claims also require context. Implementation details target Agent Substrate v0.5.0. See [Components and versions](#components-and-versions) for the full scope. This walkthrough assumes familiarity with Pods, Deployments and basic Kubernetes networking. Start with the [request lifecycle](#follow-a-request-into-a-suspended-actor), jump to the [kind demo](#try-the-counter-demo-with-kind), or use the [evaluation questions](#what-to-evaluate-in-your-own-workload) to assess the fit for your platform. The design decision: keep the workers, move the sessions An agent may execute code briefly, wait for inference or an external tool, then wait indefinitely for another user turn. Running untrusted code also creates a need for isolated environments, often with one tenant per sandbox. The combination matters: a large fleet can contain many more waiting environments than actively executing ones. Reserving CPU and memory for every waiting agent wastes capacity. Snapshot-based multiplexing is intended to let those environments retain state without remaining assigned to running compute. Source: docs/architecture.md#L17-L23. What Kubernetes still does—and what Substrate moves elsewhere The architecture document identifies three pressures behind its design: | Pressure | Why it matters for agents | |:---|:---| | API resource count and write traffic | Millions of frequently changing agent records are a different workload from ordinary Kubernetes reconciliation | | Activation latency | Pod scheduling, reconciliation, network hops and image preparation add overhead to very short executions | | State movement | Rapidly saving and restoring many environments is a different problem from attaching conventional persistent volumes | These are the project's design judgments for its intended scale, rather than universal Kubernetes limits or a claim that every Pod takes seconds to start. Kubernetes still handles infrastructure provisioning and worker Pod lifecycles. Substrate introduces a separate control plane for Actor state, assignment and resumption, so an activation does not need to wait for a fresh Pod lifecycle. Sources: docs/architecture.md#L153, docs/architecture.md#L160-L172. The important boundary is the activation path: a new turn can restore an Actor onto an existing Worker without provisioning another Pod. Database write throughput, reconciliation and environment preparation all contribute to that choice. Follow a request into a suspended Actor The execution layer combines routing, state persistence and sandbox isolation: !Agent Substrate adds a gateway, state persistence and kernel isolation above Kubernetes, compute and storage. Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026). Two lifecycles, two APIs | Resource | Meaning | Managed through | |:---|:---|:---| | ActorTemplate | Immutable workload definition, including images and snapshot settings | Substrate API and database | | WorkerPool | Defines a pool that atecontroller reconciles into a Deployment | Kubernetes CRD | | Atespace | Logical grouping and identity scope for Actors; not a Kubernetes namespace | Substrate API and database | | Actor | An instance of an ActorTemplate and the unit of suspend/resume | Substrate API and database | | Worker | A record corresponding to a worker Pod; can host multiple Actors concurrently | Substrate API and database | Sources: docs/glossary.md#L10-L23, docs/glossary.md#L38-L52. An Actor is the stateful environment that belongs to a session. A Worker is the reusable execution capacity. Suspending an Actor releases its assignment; it does not require destroying and recreating the worker Pod. Worker capacity can also permit several Actors to run concurrently. Restore first, forward second For an HTTP request targeting a suspended Actor, the activation path has four steps: 1. The router identifies the Actor from ate-target-actor. 2. The control plane assigns a Worker and coordinates restoration of the Actor's snapshot. 3. Once the assignment is ready, the router forwards the request through the Worker's tunnel to the Actor. 4. The Actor handles the request and returns its response. For an Actor that is already running, ResumeActor returns the existing assignment. The call is idempotent. Source: workflow_resume.go. Returning a response does not automatically suspend the Actor. The application or higher-level system must choose a safe suspension boundary, taking account of outstanding work and network calls. Suspend and resume !Active agent environments on Workers exchange state with idle environments persisted in storage through suspend/resume. Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026). Here, “idle” means state has been persisted and the environment removed from running compute, rather than a process simply sleeping. The illustration is conceptual: it does not imply a fixed one-to-one relationship between Workers and Actors. When a request arrives, atenet-router asks the control plane to resume the target Actor and resolve its Worker assignment. A suspended Actor is restored from its latest snapshot before traffic is forwarded. It may resume on a different Worker. The router itself does not decide whether the Actor is running; that decision belongs to the control plane. Suspending the Actor saves state and releases its assignment for reuse. Source: docs/architecture.md#L193-L204. What runs where | Component | Responsibility | Relationship to Kubernetes | |:---|:---|:---| | ate-api-server (ateapi) | Actor lifecycle, scheduling and snapshot coordination | Separate API, scheduler and workflow layer backed by PostgreSQL | | atecontroller | Reconciles WorkerPool and related CRDs | Standard controller-runtime controller | | atelet / ateom | Coordinates and executes sandbox checkpoint/restore | Additional execution layer inside worker Pod boundaries | | atenet router and egress | Resumption-aware ingress and controlled outbound traffic | Routing plus Actor lifecycle coordination | | podcertcontroller | Issues short-lived certificates for component mTLS | Signer controller for PodCertificateRequest | The threat-model diagram shows how these components connect: !Substrate architecture showing ate-api-server, atelet, ateom, atenet-router, object storage and PostgreSQL. Source: docs/threat-model.md#L30-L40. {% details Repository map: controllers, sidecars and tools %} The repository also includes supporting programs: | Directory | Role | |:---|:---| | cmd/ateapi | gRPC control plane | | cmd/atecontroller | Kubernetes controllers | | cmd/atelet | Node supervisor deployed as a DaemonSet | | cmd/ateom-gvisor | Drives gVisor inside worker Pods | | cmd/ateom-microvm | Runs Actors with Cloud Hypervisor microVMs | | cmd/objectstore-plugin | Snapshot transfer sidecar for node and control-plane operations | | cmd/atenet | Networking daemon and proxy integration | | cmd/dataplane/envoy | Envoy image with Rust Dynamic Modules for egress enforcement | | cmd/credential-provider/kubernetes-secrets | Reference credential provider backed by Kubernetes Secrets | | cmd/podcertcontroller | Pod certificate signers | | cmd/kubectl-ate | Actor and other Substrate resource operations | | cmd/ate-setup | Installation and removal | | cmd/benchmarking | Synthetic resource-consuming test workloads | | tools/setup-gcp | GKE, storage and IAM provisioning | The README's tour is not an exhaustive inventory; the sidecar, provider, dataplane and setup entries are supplemented from their own source and documentation. Sources: README.md#L260-L272, cmd/objectstore-plugin/main.go#L15-L19, cmd/ate-setup/README.md#L3, cmd/dataplane/envoy/README.md. {% enddetails %} Resource ownership and assignment !Agent Substrate between the agent application layer and Kubernetes, with sandbox isolation and storage beneath it. Source: Bringing you Agent Sandbox on GKE and Agent Substrate, Google Cloud Blog (May 20, 2026). The green sandbox-isolation block is the foundation for the checkpoint and restore operations. Agent Substrate incorporates Agent Sandbox's secure runtime and snapshot capabilities into a separate execution layer. classDiagram namespace kube-apiserver { class WorkerPool { <<CRD>> } class Deployment class WorkerPod { ateom runsc } } namespace ate-api-server { class ActorTemplate { <<record>> } class Actor { <<record>> status snapshotRefs } class Worker { <<record>> actorAssignments podIP } } ActorTemplate ..> WorkerPool : worker_selector (labels) WorkerPool ..> Deployment : reconciled by atecontroller Deployment "1" *-- "*" WorkerPod : manages Actor "*" --> "1" ActorTemplate : actor_template Actor "0..*" --> "0..1" Worker : runs on Worker "1" --> "1" WorkerPod : maps to Adapted from docs/architecture.md#L268-L303; the multiplicity reflects the implementation's support for multiple Actors per Worker. The split between API servers is central. Infrastructure resources stay in Kubernetes, while frequent Actor/Worker assignment changes go through ate-api-server and its state store. Two resource layers The architecture gives three reasons for this split: - Keep high-frequency Actor updates away from the Kubernetes control plane. - Use low-latency lookups and atomic assignments for resumption. The document's 100ms figure is a design target, not a result measured here. - Retain Kubernetes RBAC, auditing and policies for infrastructure resources such as WorkerPools and SandboxConfigs. One detail in that document is stale: it says authorization has not been implemented. The same v0.5.0 tag contains OpenFGA-based authorization. --experimental-enable-authz defaults to false and controls enforcement for ordinary Control RPCs. AccessPolicy RPCs are checked regardless of that flag. Sources: docs/architecture.md#L247-L261, cmd/ateapi/main.go#L84-L88, cmd/ateapi/internal/authz/interceptor.go#L28-L60. ate-api-server: the Actor control plane ateapi owns Actor lifecycles, schedules them onto Workers and coordinates snapshots. Its gRPC server connects to PostgreSQL, creates Kubernetes clients and configures its mTLS credentials. Sources: docs/glossary.md#L56-L58, cmd/ateapi/main.go#L509-L521, cmd/ateapi/main.go#L560-L586. templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv, *templateResyncInterval) templateReconciler.Start(shutdownCtx) workerAssignmentReconciler := controlapi.NewWorkerAssignmentReconciler(persistence, workerCache) workerAssignmentReconciler.Start(shutdownCtx) Source: cmd/ateapi/main.go#L326-L330. Despite their names, these reconcilers are internal Substrate loops rather than controller-runtime reconcilers watching custom resources. ActorTemplateReconciler prepares golden snapshots; WorkerAssignmentReconciler handles Actors lost when ateom restarts. This component does not map one-to-one to kube-apiserver: it combines Actor-specific API handling, scheduling and workflows. atecontroller: conventional Kubernetes reconciliation atecontroller uses ctrl.NewManager and registers four reconcilers, including WorkerPoolReconciler. Its core structure is familiar: func (r *WorkerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) wp := &atev1alpha1.WorkerPool{} if err := r.Get(ctx, req.NamespacedName, wp); err != nil { if k8errors.IsNotFound(err) { return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("failed to get worker pool %q: %w", req.NamespacedName, err) } if !wp.GetDeletionTimestamp().IsZero() { log.Info("WorkerPool is being deleted") return ctrl.Result{}, nil } if err := r.reconcileWorkerPool(ctx, wp); err != nil { log.Error(err, "Failed to reconcile worker pool") return ctrl.Result{}, err } return ctrl.Result{}, nil } Sources: cmd/atecontroller/main.go#L233-L276, cmd/atecontroller/internal/controllers/workerpool_controller.go#L105-L129. Reconciliation does more than produce a Deployment. The WorkerPool reconciler protects referenced SandboxConfigs with finalizers; a separate SandboxConfigProtectionReconciler releases protection. Updating the recorded SandboxConfig uses two status writes so that status never references a configuration whose protection has already been released. Owns(&Deployment{}) and Watches(&SandboxConfig{}, ...) connect these resources through normal controller-runtime mechanisms. Sources: cmd/atecontroller/internal/controllers/workerpool_controller.go#L134-L136, cmd/atecontroller/internal/controllers/workerpool_controller.go#L416-L425. atelet and ateom: checkpoint and restore atelet runs on each node as a DaemonSet. ateom runs inside worker Pods and exposes operations such as RunWorkload, CheckpointWorkload and RestoreWorkload. atelet coordinates worker supervision, image caching and CSI volumes. It also reuses kubelet's image credential-provider mechanism through --image-credential-provider-config. Sources: docs/architecture.md#L324-L326, cmd/atelet/main.go#L103-L104. Snapshot upload and download are delegated to objectstore-plugin sidecars over a Unix-socket gRPC API: | Plugin role | Operations | |:---|:---| | NodeProvider, alongside atelet | Upload and fetch snapshot files | | ControlProvider, alongside ate-api-server | Copy and delete whole snapshots | The main processes do not directly transfer snapshots to object storage. This does not mean they hold no credentials at all: mTLS and database authentication remain separate concerns. Source: docs/architecture.md#L330. For gVisor, cmd/ateom-gvisor/runsc.go invokes runsc checkpoint and runsc restore. The microVM implementation uses Kata integration and Cloud Hypervisor, with userfaultfd demand paging when restoring memory. Sources: docs/architecture.md#L328-L338. Kubelet and the container runtime still run the worker Pod itself. Substrate manages sandboxed Actors inside it. Kubernetes already has a Kubelet Checkpoint API; Substrate's distinction is coordinating persistence, assignment, restore and reconnection as an Actor lifecycle. atenet: routing that can resume its destination Ingress requests identify an Actor with the ate-target-actor header, formatted as <atespace>/<actor>. In the Envoy configuration described here, an ext_proc processor calls the control plane's ResumeActor RPC before resolving the destination. Once the assignment is available, the router opens an mTLS CONNECT tunnel to the Worker's atunnel listener on port 8443. atunnel, hosted by ateom, forwards traffic over the Actor's private veth interface. Worker Pod port 80 is not a direct Actor ingress endpoint. Sources: docs/architecture.md#L344-L354. The Kubernetes analogy is Service/Ingress/Gateway routing with an extra orchestration step: restore the destination if necessary before forwarding to it. podcertcontroller: Pod certificate signers podcertcontroller provides three signers: | Signer | Purpose | |:---|:---| | servicedns.podcert.ate.dev/identity | Service DNS certificates backed by a local CA | | podidentity.podcert.ate.dev/identity | Pod identity certificates equivalent to Kubernetes service account identity | | postgres.podcert.ate.dev/identity | PostgreSQL login certificates for ate-api-server | Source: cmd/podcertcontroller/main.go#L15-L22. It watches certificates.k8s.io/v1 PodCertificateRequest objects. The API became GA in Kubernetes v1.37: PodCertificateRequest: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.37"), Default: true, PreRelease: featuregate.GA}, // LockToDefault in 1.38 }, Source: kube_features.go. The API lets kubelet request certificates and project them into Pods. A signer must still process those requests. The well-known kubernetes.io/kube-apiserver-client-pod signer described in the API comments is unimplemented in this version. Substrate fills its own needs with the three signers above. Its README calls the controller a “polyfill” for the missing signers. Sources: certificate API types, README.md#L268-L269. Keep session state and API credentials separate Isolation, identity and credentials are separate controls !The agent environment combines identity-based ingress and egress, secrets management, kernel isolation and rapid suspend/resume. Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026). Kernel isolation does not by itself protect external API credentials. That also requires the gateway and credential-provider configuration described below. The security document distinguishes Met, In Progress, Future and Out of Scope. Selected principles in v0.5.0 are: | Principle | Meaning | Documented status | |:---|:---|:---| | Always Sandboxed | Run Actors in gVisor or Cloud Hypervisor microVMs rather than relying on ordinary Linux containers alone | Met | | Zero Ambient Credentials | Do not implicitly give Actors infrastructure permissions or external API keys | Met; distinct from demos that explicitly pass keys in environment variables | | No Access by Default | Restrict Actor access to the control plane, separate from required infrastructure traffic | Loopback/private netns: Met; DNS filtering: In Progress | | Fail-Closed Policy-Enforced Egress Gateway | Control outbound traffic through an egress proxy | Gateway: Met; fail-closed behavior: In Progress, with UDP passthrough noted | Sources: docs/security.md#L30-L33, docs/security.md#L41, docs/security.md#L59, docs/security.md#L75-L77. Warning: The same document lists Atespace authorization as In Progress, and node blast-radius containment and storage encryption as Future. The security architecture is not evidence that every control is fully implemented. Ordinary Control RPC authorization still requires the experimental flag. Inject credentials outside the Actor With egress credential injection, an Actor sends an HTTPS request containing a placeholder header. The gateway obtains the real credential and substitutes it outside the sandbox: 1. The Actor sends the request with a placeholder value. 2. The egress gateway terminates TLS and matches an EgressPolicy header-replacement rule. 3. The credential provider authorizes access and reads the secret. 4. The gateway replaces the header and makes a separate TLS connection to the destination. The Actor must trust the gateway's CA. If the request does not contain the header to replace, credential lookup and replacement do not occur. This keeps the API key outside Actor memory and snapshots. It does not prevent misuse of an allowed API; destination restrictions and application authorization still matter. Sources: docs/egress-credential-injection.md#L1-L15, docs/egress-credential-injection.md#L41-L54. The reference kubernetes-secrets provider starts with an empty deny-all policy. Readable namespaces must be explicitly allowed for each Atespace. Actors do not read Kubernetes Secrets themselves. Source: README.md#L132-L136. EgressPolicy defines destinations and header replacement An EgressPolicy specifies allowed traffic and header replacement rules. The credential provider separately decides whether the Actor can use the referenced secret. The API defines the policy as an unordered set of allows: traffic matching no rule is denied. Source: pkg/proto/ateapipb/ateapi.proto#L609-L610. For example, allow HTTPS to api.example.com and replace its Authorization header: rules: - https: hostnames: ["api.example.com"] effects: replaceHeaders: - header: Authorization prefix: "Bearer " credentialUri: ate-secret://k8s.io/default/ns1/example-api/token Source: docs/egress-credential-injection.md#L69-L76. The policy is associated with an Actor rather than embedded in its ActorTemplate: # Create an egress policy. kubectl ate create egress-policy <actor-name> -a <atespace> -f policy.yaml Source: cmd/kubectl-ate/README.md#L247-L253. Note: The Claude Code demo discussed later directly places ANTHROPIC_API_KEY in the ActorTemplate environment. That differs from gateway injection. In either case, the Actor needs an egress policy permitting its HTTPS destination, such as api.anthropic.com. The complete request path With routing and credential injection in place, an HTTP-triggered agent can resume and call an external LLM as follows. This combines the documented flows; Counter itself does not call an LLM. sequenceDiagram actor User as User / higher-level system participant Router as atenet-router participant API as ate-api-server participant Atelet as atelet participant Tunnel as ateom / atunnel participant Agent as Actor participant Egress as atenet-egress participant Cred as credential provider participant LLM as External LLM API User->>Router: HTTP request (ate-target-actor) Router->>API: ResumeActor alt Actor is suspended API->>Atelet: Restore request Atelet->>Tunnel: RestoreWorkload Tunnel-->>Atelet: Restore complete Atelet-->>API: Complete else Actor is running Note over API: Return existing assignment end API-->>Router: Worker assignment Router->>Tunnel: Forward through mTLS CONNECT Tunnel->>Agent: Forward request Agent->>Egress: HTTPS (placeholder header) Egress->>Cred: Request credential Cred-->>Egress: Return API key after authorization Egress->>LLM: Replace header and send over a separate TLS connection LLM-->>Egress: Response Egress-->>Agent: Response Agent-->>Tunnel: Result Tunnel-->>Router: Response Router-->>User: Response Note over User,API: A higher-level system can request SuspendActor to save state and release the assignment Sources: Resume sequence, credential injection. Cold boot without a snapshot and other branches are omitted. The Actor-to-gateway and gateway-to-provider connections use separate TLS sessions. The gateway must terminate the first to replace headers in the second. Try the Counter demo with kind The Counter workload makes state preservation visible: each request increments counters in process memory, a file and the root filesystem. The example below uses gVisor and includes recorded output from a local kind run. Set up the cluster and demo Building v0.5.0 requires Go 1.27.0 and a working Docker-based development environment. The microVM option additionally requires /dev/kvm and suitable virtualization support. Sources: go.mod#L1-L5, hack/create-kind-cluster.sh#L74-L87. Use a dedicated checkout and cluster name. The creation script deletes and recreates an existing cluster with the same name, so choose one that does not overlap with an environment you want to keep. git clone --branch v0.5.0 --depth 1 \ https://github.com/agent-substrate/substrate.git substrate-v0.5.0 cd substrate-v0.5.0 export KIND_CLUSTER_NAME=ate-overview # create cluster and local registry (IPv4; IP_FAMILY=dual|ipv6 overrides) hack/create-kind-cluster.sh # install ate, PostgreSQL, rustfs hack/install-ate-kind.sh --deploy-ate-system \ --credential-provider='{"name":"k8s.io"}' # install counter demo hack/install-ate-kind.sh --deploy-demo-counter # install kubectl-ate go install ./cmd/kubectl-ate # create a counter actor in the demo's atespace (--template names the # actor template, resolved in the actor's atespace) kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter # port-forward the network router to bind to local port `8000` kubectl port-forward -n ate-system svc/atenet-router 8000:80 Source: README.md#L111-L129. The creation script sets up a local registry and a kind cluster with kindest/node:v1.37.0. It explicitly enables certificate-related feature gates, including ClusterTrustBundleProjection; some comments still describe v1.36 requirements. Source: hack/create-kind-cluster.sh#L104-L113. The system installation uses ko to build and push its Go components and deploys PostgreSQL, RustFS, CRDs and SandboxConfig resources. The Counter installation adds its WorkerPool and ActorTemplate and waits for a golden snapshot. WorkerPool and ActorTemplate The demo's WorkerPool includes the following settings. The installer resolves template placeholders and ko:// references before applying the configuration. apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: counter namespace: ate-demo-counter labels: workload: counter spec: replicas: 3 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor sandboxClasses: - name: gvisor template: resources: limits: cpu: "1" memory: 1Gi requests: cpu: 250m memory: 1Gi Source: demos/counter/counter.yaml.tmpl#L26-L63. An ActorTemplate is not a Kubernetes object. This YAML maps to Substrate's protobuf fields and is passed through kubectl ate create actor-template: metadata: atespace: ate-demo-counter name: counter workerSelector: matchLabels: workload: counter containers: - name: counter image: ko://github.com/agent-substrate/substrate/demos/counter wakeupProbe: httpGet: path: /readyz port: 80 volumeMounts: - name: data mountPath: /home/counter resources: limits: - name: cpu quantity: "1" - name: memory quantity: 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY storageLocation: gs://${BUCKET_NAME}/ate-demo-counter/ sandboxConfig: sandboxClass: gvisor configName: gvisor-default volumes: - name: data durableDir: {} Source: demos/counter/counter-template.yaml.tmpl#L22-L69. SNAPSHOT_FIDELITY_MEMORY requests preservation of process memory. gvisor-default names a cluster-scoped SandboxConfig whose assets specify where to download the runtime binaries: apiVersion: ate.dev/v1alpha1 kind: SandboxConfig metadata: name: gvisor-default annotations: sandboxconfig.ate.dev/is-class-default: "true" spec: sandboxClass: gvisor defaultVersion: v3 versions: - name: v3 state: Enabled pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4" assets: arm64: gvisor: url: "gs://gvisor/releases/nightly/2026-10-10/aarch64/gvisor.tar.zstd" sha256: "aaf414f06eea7960238e43165c691f0a36cefc0593fb141e011e96211c7b6f57" Source: manifests/ate-install/sandboxconfig-gvisor.yaml#L22-L48. Only the arm64 assets are shown; amd64 assets are omitted. Observe Actor state and preserved counters A newly created Counter Actor starts suspended: its snapshot exists, but it has no Worker assignment yet. kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter Recorded output: ATESPACE NAME TEMPLATE STATE WORKER POD WORKER IPS VERSION AGE ate-demo-counter my-counter-1 ate-demo-counter/counter ACTOR_STATE_SUSPENDED <none> 1 0s Send a request through the router. It resumes the Actor before forwarding, and the recorded response is HTTP 200: curl -X POST \ -H "ate-target-actor: ate-demo-counter/my-counter-1" \ -i http://localhost:8000/ Recorded output: HTTP/1.1 200 OK Content-Length: 111 Content-Type: text/plain; charset=utf-8 hello from: 169.254.17.2 | preserved memory count: 1 | preserved file counter: 1 | preserved rootfs counter: 1 A second POST increments the counter to 2. The Actor is now running and assigned to a worker Pod: curl -X POST \ -H "ate-target-actor: ate-demo-counter/my-counter-1" \ http://localhost:8000/ kubectl ate get actor my-counter-1 -a ate-demo-counter Recorded output: ATESPACE NAME TEMPLATE STATE WORKER POD WORKER IPS VERSION AGE ate-demo-counter my-counter-1 ate-demo-counter/counter ACTOR_STATE_RUNNING ate-demo-counter/counter-796d677995-v55tv 10.244.0.23 3 20s Explicitly suspend it, then send another request: kubectl ate suspend actor my-counter-1 -a ate-demo-counter curl -X POST \ -H "ate-target-actor: ate-demo-counter/my-counter-1" \ http://localhost:8000/ Recorded output: hello from: 169.254.17.2 | preserved memory count: 3 | preserved file counter: 3 | preserved rootfs counter: 3 The memory, file and root-filesystem counters continue at 3. This illustrates the documented snapshot-and-resume lifecycle, including preserved process memory. Stop the port-forward when finished, then remove the dedicated cluster. The kind-registry container is separate and remains after cluster deletion. hack/kind.sh delete cluster --name ate-overview Plan the deployment around more than Pods Worker Pods and CRDs are only part of the deployment. The following table distinguishes infrastructure that must be supplied from components the installer can deploy: | Dependency | Purpose | Installation considerations | |:---|:---|:---| | PostgreSQL | Actor/Worker state store | The kind setup includes a development database; production operators supply database identities, schema and connection settings | | GCS or S3-compatible object storage | Durable snapshot storage | Configure storage.bucketName / BUCKET_NAME; kind uses ate-snapshots and an in-cluster RustFS service | | Egress credential provider | Resolve credentials outside Actors | Provider selection is required, including explicit {"enabled":false} to disable injection; {"name":"k8s.io"} deploys the reference provider | | Sandbox runtime binaries | Run Actors with gVisor or microVMs | Selected through a cluster-scoped SandboxConfig; default gVisor assets come from a public GCS bucket | | Proxy dataplane image | Router and egress proxies | --atenet-dataplane selects Envoy, requiring its custom build, or agentgateway, using a published image | | Object-storage permissions | Let plugin sidecars transfer snapshots | For GCS, configure the required Workload Identity/IAM bindings; the kind substitute uses local credentials | | CSI driver, when needed | Attach CSI-backed persistent storage to Actors | Not needed for the demo's durableDir; Substrate imposes additional controller/node deployment requirements | External databases are not provisioned by ateapi or ate-setup. Snapshot-bucket selection and credential-provider selection also need explicit configuration, even when other defaults are available. Sources: docs/postgres.md#L8, docs/operator-install.md#L314, docs/egress-credential-injection.md#L128-L131. Missing GCS IAM bindings can produce a 403 on the first suspend/resume, and also prevent control-plane snapshot operations and image pulls. Source: tools/setup-gcp/README.md#L154-L159. Note: Substrate directly orchestrates CSI operations from ateapi and atelet. A CSI controller must be reachable as a cluster-network gRPC service, unlike the usual controller-side Unix socket arrangement. See docs/csi-deployment.md#L5. For the Kubernetes v1.37 setup covered here, Pod certificate and ClusterTrustBundle features are GA and enabled by default. Substrate also includes its certificate controller. Provider-specific installation requirements should still be checked separately. Source: ClusterTrustBundle feature definition. GKE availability Google announced Agent Substrate availability on GKE on September 16, 2026. The GKE offering installs open-source Substrate onto GKE Standard; Autopilot is not supported. Evaluation and non-production use are available, while production support is allowlisted under a private GA program. Substrate has no additional product charge, but the GKE and other resources created still incur their normal charges. Source: Install Agent Substrate on GKE. The announcement also describes ComputeClasses and optional Filestore agent volumes. Shared workspace storage serves a different purpose from snapshots of execution state. Google reports sub-500ms resumption, more than 500 suspend/resume activations per second and 10x density. These are Google's published figures, separate from upstream design targets and the Counter example. Source: Agent Substrate on GKE, Google Cloud Blog (September 16, 2026). What to evaluate in your own workload Substrate is worth examining when a fleet has many isolated, stateful environments but only a fraction need to execute at once. The useful question is how much resident capacity suspend/resume can release, and whether restoration meets the application's latency needs. Start with three checks: - Suspension boundaries: identify when an environment can safely stop, including outstanding network calls and application work. - State requirements: decide what must survive, then select snapshot fidelity and any persistent workspace storage accordingly. - Operational ownership: account for the database, snapshot backend, identity and egress services as well as the Workers. The Counter demo exposes the core mechanism. An agent platform must then connect it to real session events and measure its own activation latency and capacity use. Further implementation details {% details Claude Code demo: workload execution versus UI state %} What the Claude Code demo actually implements claude-code-multiplex defines three Claude Code Actors—luna, mars and orion—with a two-Pod WorkerPool. It uses the control API rather than sending workload HTTP through atenet-router, so it does not use the routing header. Source: demos/claude-code-multiplex/README.md#L14-L21. An ActorTemplate supplies the task and interval as environment variables: env: - name: ACTOR_NAME value: "luna" - name: TASK value: "Tell me one short, surprising fact about the Moon. One sentence." - name: INTERVAL_SECONDS value: "45" - name: ANTHROPIC_API_KEY value: "${ANTHROPIC_API_KEY}" Source: demos/claude-code-multiplex/agent-luna-template.yaml.tmpl#L34-L42. The workload repeatedly runs claude --print and then sleeps: while true; do ... claude --print "${TASK}" 2>&1 || echo "[demo-actor:${ACTOR_NAME}] claude exited non-zero" ... sleep "${INTERVAL_SECONDS}" done Source: demos/claude-code-multiplex/workload/run.sh#L17-L49. That idle interval is an opportunity for multiplexing. However, the v0.5.0 UI does not implement everything its README's task-flow description suggests: | UI behavior | Implementation | |:---|:---| | “Give a task” | Records the selected Actor and task in UI-server assignment state | | queued / running / completed | Derived from timestamps and randomized waiting/execution durations | | Actor state and logs | Retrieved from the Substrate and Kubernetes APIs | giveTask does not send a task to the Actor or call ResumeActor / SuspendActor. A comment in computeState identifies the task state as a time-based UI display rather than Substrate state. Source: demos/claude-code-multiplex/ui/server.go#L252-L315. A changing UI status therefore does not establish that a real LLM task completed or automatic multiplexing occurred. The useful pieces are the ActorTemplates, the periodic Claude Code workload and the state-viewing UI. Verify suspend/resume through real API operations and Actor state, as in the [Counter demo](#try-the-counter-demo-with-kind). {% enddetails %} {% details RustFS: the local snapshot backend %} RustFS RustFS is a separate, Rust-based S3-compatible object store. The kind installation uses it as its local snapshot backend. The v0.5.0 manifest selects rustfs/rustfs:1.0.0-beta.3. This development setup does not replace production storage availability, persistence or operational design. objectstore-plugin uses RustFS through its S3 backend; GCS uses the same plugin interface with a different backend. Sources: manifests/ate-install/kind/rustfs.yaml#L55-L75, rustfs/rustfs. {% enddetails %} {% details ReBAC: deriving permissions from relationships %} ReBAC and Kubernetes RBAC Substrate's API authorization uses OpenFGA's relationship-based access control model. In this version, ordinary Control RPC enforcement remains experimental and disabled by default. Source: docs/security.md#L46. Kubernetes RBAC checks applicable RoleBindings and ClusterRoleBindings against Role/ClusterRole rules: API groups, resources, verbs, namespaces and sometimes resource names. It does not automatically derive access from ownership or arbitrary parent-child relationships. Source: Using RBAC Authorization. ReBAC derives access from relations between subjects and objects, and potentially relations between objects. Inheritance is explicit in the authorization model, rather than automatic for every parent-child association. Source: OpenFGA: Modeling—Getting Started. Substrate's .fga model makes a Worker's read permission depend on its parent global object's viewer or controller relation: <!-- OpenFGA DSL; Python fencing provides partial syntax highlighting only. --> type worker relations define parent_global: [global] define host_node: [node] define can_get: viewer from parent_global or controller from parent_global Source: cmd/ateapi/internal/authz/model.fga#L29-L34. An Atespace can inherit its owner from its parent global object: type atespace relations define parent_global: [global] define owner: [user] or owner from parent_global An ActorTemplate can derive read permission from the viewer relation on its parent Atespace: type actor_template relations define parent_atespace: [atespace] define can_get: viewer from parent_atespace Sources: cmd/ateapi/internal/authz/model.fga#L71-L77, cmd/ateapi/internal/authz/model.fga#L101-L106. The relation data connects global → atespace → actor_template and global → worker. Permission checks follow those modelled relations. The practical difference from ordinary Kubernetes RoleBindings is the ability to derive child access from parent relations according to an explicit model. {% enddetails %} Components and versions | Component | Version | Repository | Scope | |:---|:---|:---|:---| | Agent Substrate | v0.5.0 | agent-substrate/substrate | Implementation, configuration and source examples | | Kubernetes | v1.37.1 | kubernetes/kubernetes | Pod certificate APIs and feature gates | | kind | v0.33.0 | kubernetes-sigs/kind | Local example using kindest/node:v1.37.0 | GKE availability information reflects the official documentation checked on October 20, 2026. The GKE installer does not necessarily distribute the same version as the upstream tag used here. Note: Agent Substrate is pre-1.0 and does not guarantee backward compatibility. Unless another repository is named, source links in this article refer to agent-substrate/substrate v0.5.0. References - Kubernetes Podcast #272: Agent Substrate - Agent Substrate repository - Bringing you Agent Sandbox on GKE and Agent Substrate - Agent Substrate on GKE announcement - Install Agent Substrate on GKE - Architecture - Threat model - Glossary - Security guide -
2026 깃허브 오픈소스 AI 에이전트 생태계 총정리 00:00 2026 최신 깃허브 오픈소스 AI 에이전트 생태계 소개 01:40 AI 에이전트 생태계의 전반적인 조감도와 4대 영역 파악 03:05 코어 개발 환경(IDE)과 AI의 융합 및 클로드 코드와 젯브레인 비교 06:08 멀티 에이전트 오케스트레이션과 로플로, 오차드, 서브스트레이트 기술 10:39 과학 연구 및 CAD 생성을 위한 에이전트 특수 스킬 부여 10:45 에이전트 전용 고속 검색 엔진과 지능 확장 메모리 10:50 커널 레벨 샌드박스를 통한 안전한 통제망과 보안 API 라우팅 14:29 헤드리스 디자인 에디터와 미니멀한 프로젝트 관리 공간 16:32 사물 중심의 스큐어모피즘을 적용한 데스크톱 환경 16:37 야간 세션 유지, 저수준 그래픽 제어 및 수학적 검증 유틸리티 19:23 AI 결과물의 품질 관리와 간결하고 직관적인 텍스트 사용자 경험 21:49 에이전트의 사고 과정 시각화와 조율자로서의 개발자 역할 결론 Claude Code Plugins Directory : anthropics/claude-code-plugins JetBrains Plugin : plugins.jetbrains.com Scientific Agent Skills : K-Dense-AI/scientific-agent-skills ruflo : ruvnet/ruflo ECC : affaan-m/ECC XERJ : xerj-org/xerj text-to-cad : earthtojake/text-to-cad substrate : agent-substrate/substrate OpenShell : NVIDIA/OpenShell FreeLLMAPI : tashfeenahmed/freellmapi Orchard : openai/orchard OpenPencil : open-pencil/open-pencil Gr8r Studio : gr8rstudio/gr8r-studio Insomnia : krishhgg/Insomnia BRUTAL Framework : ahwootech/BRUTAL librecalculator : tonyzedginidze/librecalculator gpt-with-taste : NB-Corp/gpt-with-taste cc-stretch : pro-vi/cc-stretch DEsk : fyzugx/DEsk Remory : fudan-generative-vision/remory agentplay : Entelligence-AI/agentplay [en-US] 2026 GitHub Open Source AI Agent Ecosystem Overview 00:00 Introduction to the Latest GitHub Open Source AI Agent Ecosystem (2026) 01:40 An Overview of the AI Agent Ecosystem and Understanding the Four Key Areas 03:05 Convergence of Core Development Environments (IDEs) and AI, and a Comparison of Claude Code and JetBrains 06:08 Multi-Agent Orchestration and Loflow, Orchard, and Substrate Technologies 10:39 Granting Agent Special Skills for Scientific Research and CAD Generation 10:45 Agent-Dedicated High-Speed Search Engine and Intelligent Scalable Memory 10:50 Secure Control Network and Secure API Routing via Kernel-Level Sandbox 14:29 Headless Design Editor and Minimal Project Management Space 16:32 Object-Centric Skeuomorphism Desktop Environment 16:37 Night Session Maintenance, Low-Level Graphics Control, and Mathematical Verification Utilities 19:23 Quality Control of AI Deliverables and Concise and Intuitive Text User Experience 21:49 Visualization of Agent Thought Processes and the Developer's Role as a Coordinator Conclusion Claude Code Plugins Directory: anthropics/claude-code-plugins JetBrains Plugin: plugins.jetbrains.com Scientific Agent Skills: K-Dense-AI/scientific-agent-skills ruflo: ruvnet/ruflo ECC: affaan-m/ECC XERJ: xerj-org/xerj text-to-cad: earthtojake/text-to-cad substrate: agent-substrate/substrate OpenShell: NVIDIA/OpenShell FreeLLMAPI: tashfeenahmed/freellmapi Orchard: openai/orchard OpenPencil: open-pencil/open-pencil Gr8r Studio: gr8rstudio/gr8r-studio Insomnia: krishhgg/Insomnia BRUTAL Framework: ahwootech/BRUTAL librecalculator: tonyzedginidze/librecalculator gpt-with-taste: NB-Corp/gpt-with-taste cc-stretch: pro-vi/cc-stretch DEsk: fyzugx/DEsk Remory: fudan-generative-vision/remory agentplay: Entelligence-AI/agentplay [id] Rangkuman Ekosistem AI Agent Open Source GitHub 2026 00:00 Pengantar Ekosistem Agen AI Open Source GitHub Terbaru (2026) 01:40 Gambaran Umum Ekosistem Agen AI dan Memahami Empat Area Utama 03:05 Konvergensi Lingkungan Pengembangan Inti (IDE) dan AI, dan Perbandingan Claude Code dan JetBrains 06:08 Orkestrasi Multi-Agen dan Teknologi Loflow, Orchard, dan Substrate 10:39 Memberikan Keterampilan Khusus Agen untuk Penelitian Ilmiah dan Generasi CAD 10:45 Mesin Pencari Berkecepatan Tinggi Khusus Agen dan Memori Cerdas yang Dapat Diperluas 10:50 Jaringan Kontrol Aman dan Perutean API Aman melalui Sandbox Tingkat Kernel 14:29 Editor Desain Tanpa Antarmuka Pengguna dan Ruang Manajemen Proyek Minimal 16:32 Lingkungan Desktop Skeuomorfisme Berpusat pada Objek 16:37 Pemeliharaan Sesi Malam, Kontrol Grafis Tingkat Rendah, dan Verifikasi Matematis Utilitas 19:23 Kontrol Kualitas Hasil AI dan Pengalaman Pengguna Teks yang Ringkas dan Intuitif 21:49 Visualisasi Proses Berpikir Agen dan Peran Pengembang sebagai Koordinator Kesimpulan Direktori Plugin Claude Code: anthropics/claude-code-plugins Plugin JetBrains: plugins.jetbrains.com Keterampilan Agen Ilmiah: K-Dense-AI/scientific-agent-skills ruflo: ruvnet/ruflo ECC: affaan-m/ECC XERJ: xerj-org/xerj text-to-cad: earthtojake/text-to-cad substrate: agent-substrate/substrate OpenShell: NVIDIA/OpenShell FreeLLMAPI: tashfeenahmed/freellmapi Orchard: openai/orchard OpenPencil: open-pencil/open-pencil Gr8r Studio: gr8rstudio/gr8r-studio Insomnia: krishhgg/Insomnia BRUTAL Framework: ahwootech/BRUTAL librecalculator: tonyzedginidze/librecalculator gpt-with-taste: NB-Corp/gpt-with-taste cc-stretch: pro-vi/cc-stretch DEsk: fyzugx/DEsk Remory: fudan-generative-vision/remory agentplay: Entelligence-AI/agentplay