substrate
How AX depends on Agent Substrate (modules, address contract, mocks, version bumps, E2E pin). Load when touching internal/substrate, bumping Substrate, or triaging make test-e2e.
소스 정보
- 저장소
- google/ax
- 최근 소스 활동
- 2026년 11월 2일 23:16
- 감지된 SKILL.md 언어
- 영어
- 스타
- 14,575
- 포크
- 784
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- substrate
- description
- How AX depends on Agent Substrate (modules, address contract, mocks, version bumps, E2E pin). Load when touching internal/substrate, bumping Substrate, or triaging make test-e2e.
# Agent Substrate in AX
AX schedules every `Task` as a sandboxed **actor** on
[Agent Substrate](https://github.com/agent-substrate/substrate). AX owns the
declarative layer (Task/Workspace/Model, reconciler, CLI); Substrate owns
isolation, checkpoint/restore, worker placement and in-cluster routing.
Facts below were verified against `main` on 2026-10-28 — re-check `go.mod`
and `hack/setup-e2e-kind.sh` if they look stale.
## 1. Module map
| Module | Pinned | Provides | Imported by |
|---|---|---|---|
| `go.ate.dev/substrate` | `v1.0.0-rc.1` | Control API protos `api/ateapi/v1` (atespaces, actor templates, actors, suspend/resume) | `internal/substrate`, `internal/server`, `internal/controller`, `test/e2e` |
| `github.com/agent-substrate/env` | `v0.0.11-0.20260912052224-4468a200b170` (pseudo-version) | Guest services served inside the sandbox (process exec + file system gRPC) and `proto/ateenv/v1alpha` | `internal/guest`, `internal/metadata` |
There is no `replace` directive for either module. Local hacking against a
Substrate checkout goes through `SUBSTRATE_DIR` in E2E (section 5).
## 2. The Control API wrapper: `internal/substrate.Client`
All Control API traffic goes through this package. It encapsulates:
- **Endpoint defaults** — `api.ate-system.svc.cluster.local:443`; authority
and TLS `ServerName` default to `api.ate-system.svc` when the target
contains that string.
- **Auth** — bearer token from `SUBSTRATE_TOKEN_FILE` (in-cluster: projected SA
token with audience `api.ate-system.svc`, see `deploy/ax-server.yaml`), CA
from `SUBSTRATE_CA_FILE`.
- **Retry** — `IsTransientError` + `retryOnCodes` for `Unavailable`-class errors.
- **Helpers** the reconciler relies on — `EnsureAtespace`, `EnsureActorTemplateWithImage`,
`EnsureActor`, `SuspendActor`, `ResumeActor`, `DeleteActor`, `WorkerIP`,
`ResourceLimits`, `BuildActorTemplate`.
### Where a new Control API call lives today
- `internal/substrate/client.go` is the only non-test file that calls
`ateapipb.NewControlClient`; every existing RPC is a method on `*Client`,
wrapped in `retryOnCodes(..., codes.Unavailable)` where idempotent.
- Each method has a table-driven case in `client_test.go` (`mockControlClient`).
- Callers in `internal/controller` exercise it through `mockControlServer`;
`internal/server` and `cmd/ax` tests use `internal/substrate/substratetest`
(section 4).
## 3. Address contract
| Value | Occurrences |
|---|---|
| `api.ate-system.svc.cluster.local:443` + authority `api.ate-system.svc` | `cmd/ax-server/main.go` (`--substrate-endpoint` / `--substrate-authority` flag defaults), `internal/substrate/client.go` (3×), `deploy/ax-server.yaml`, `test/e2e/manifests/ax-server.yaml`, `test/e2e/harness_test.go` |
| `atenet-router.ate-system.svc[.cluster.local]` | `internal/controller/reconciler.go` (`defaultRouterServerName`), `internal/tunnel/router_tls.go` (`routerServerName`), `cmd/ax/main.go` (ssh port-forward path), `internal/guest/client.go` (doc comment), `deploy/ax-server.yaml`, `test/e2e/manifests/ax-server.yaml`, `docs/networking.md` |
| namespace `ate-system` | all of the above + `test/e2e/harness_test.go` (`ateSystemNamespace`) |
Why it matters: tasks never get a Service/Ingress. Every request to a task is
proxied by the **atenet router**, which picks the target from the
`ate-target-actor` header and resumes a suspended actor on demand
(`docs/networking.md`). `ax ssh` port-forwards to `svc/atenet-router` on 443
(mTLS) and falls back to 80.
## 4. Testing against Substrate
### Unit tests: in-process mock, no cluster
```go
type mockControlServer struct {
ateapipb.UnimplementedControlServer
// record calls / inject failures here
}
grpcServer := grpc.NewServer()
ateapipb.RegisterControlServer(grpcServer, mockSrv)
// serve on a listener, point substrate.NewClient at it
```
Canonical examples: `internal/controller/reconciler_test.go` (reconciler state
transitions; the largest test file in the repo). `internal/server/server_test.go`
and `cmd/ax/main_test.go` run a real `TaskReconciler` against
`internal/substrate/substratetest`, an in-memory fake of the same API.
`internal/substrate/client_test.go` uses a client-side `mockControlClient`
instead. No mocking library is in `go.mod`.
### Substrate behaviour the code already depends on
- Actor template images must be digest-pinned (`@sha256:…`); a tag-only
`spec.image` fails at ActorTemplate creation with reason
`TemplateCreationFailed` (`docs/manifests.md`). `make images` writes
`bin/ax-server.digest` / `bin/ax-task-runner.digest` for this reason.
- After `ResumeActor` the actor may be on a different worker, so AX never
stores a worker IP: the server reads it from the actor on every get and list
(`substrate.WorkerIP` in `internal/server/server.go`), and the reconciler
uses the IP `ResumeActor` returns only for its readiness probe
(`internal/controller/reconciler.go`).
- Guest services (process + fs) are mounted by `internal/metadata/server.go`
only when `task.Spec.Debug` is true; `ax ssh` (`cmd/ax/main.go`) refuses to
connect otherwise.
- Readiness: Substrate and the reconciler probe the runner on port 80
(`/healthz`, `/readyz?check=workspace`, served by `internal/metadata`);
`/readyz` is 503 until all workspaces are materialized (`docs/sandbox.md`).
- Snapshot storage: Actor templates created by AX leave `SnapshotConfig.StorageLocation`
empty by default so Substrate uses its cluster-wide `--snapshot-location`. Specifying a custom
snapshot bucket (`snapshotsBucket` in `BuildActorTemplate`) can only be used when the
Substrate cluster has the `PerTemplateSnapshotLocation` preview feature gate enabled;
otherwise Substrate rejects template creation with
`actor_template.snapshot_config.storage_location: Forbidden`.
## 5. E2E and the Substrate version pin
`test/e2e` is compiled by `go test ./...` but **self-skips** unless
`AX_E2E=true` or `-args --e2e`. The real runs go through `make`:
| Target | Substrate version | When |
|---|---|---|
| `make test-e2e` | exactly `go list -m -f '{{.Version}}' go.ate.dev/substrate` (pseudo-versions reduced to the 12-char commit) | PR CI (`e2e.yml`) |
| `make test-e2e SUBSTRATE_REF=<branch\|tag\|sha>` | override | local, or `workflow_dispatch` input `substrate_ref` in `e2e.yml` |
| `make test-e2e-head` | Substrate `main` | nightly schedule in `e2e.yml` |
| `make e2e-setup` / `e2e-run` / `e2e-cleanup` | same as above | iterate without re-provisioning |
Useful env (full list in `test/e2e/README.md`): `SUBSTRATE_DIR` (local
checkout; a detached worktree is created under `.tmp/` if HEAD ≠ ref),
`SUBSTRATE_REPO`, `SUBSTRATE_IMAGE_CACHE_DIR` (CI caches built Substrate
images keyed by the Substrate version, so a version change is a cache miss),
`E2E_ARGS='-test.run TestE2E_ModelCRUD'`.
## 6. Bumping Substrate
What the repo mechanically requires when `go.ate.dev/substrate` or
`github.com/agent-substrate/env` changes in `go.mod`:
```bash
go get go.ate.dev/substrate@vX.Y.Z # and/or github.com/agent-substrate/env@<sha>
go mod tidy # CI diffs go.mod/go.sum
go build ./... && go test ./... # unit tests compile against the new ateapipb
make test-e2e # hack/setup-e2e-kind.sh installs the go.mod version
```
`e2e.yml` keys its Substrate image cache on the module version, so the first
E2E run after a bump rebuilds Substrate.
GitHub에서 보기