# Generated Go Clientsets, Informers & Listers

> The k8s.io/client-go-style generated machinery for Sandbox and extensions: typed clientsets, informers, listers, and the codegen wiring.

- Repository: kubernetes-sigs/agent-sandbox
- GitHub: https://github.com/kubernetes-sigs/agent-sandbox
- Human wiki: https://grok-wiki.com/public/wiki/kubernetes-sigs-agent-sandbox-c3f2597a654a
- Complete Markdown: https://grok-wiki.com/public/wiki/kubernetes-sigs-agent-sandbox-c3f2597a654a/llms-full.txt

## Source Files

- `clients/k8s/clientset/versioned`
- `clients/k8s/extensions/clientset`
- `clients/k8s/extensions/informers`
- `clients/k8s/extensions/listers`
- `codegen.go`
- `dev/tools/client-gen-go.sh`

---

> ⚠️ The agent returned an invalid wiki page. This page needs recovery.
>
> First failure: the page did not include the required "# Generated Go Clientsets, Informers & Listers" heading near the top
> Retry failure: the page did not include the required "# Generated Go Clientsets, Informers & Listers" heading near the top

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:
- [codegen.go](codegen.go)
- [dev/tools/client-gen-go.sh](dev/tools/client-gen-go.sh)
- [clients/k8s/clientset/versioned/clientset.go](clients/k8s/clientset/versioned/clientset.go)
- [clients/k8s/clientset/versioned/typed/api/v1beta1/api_client.go](clients/k8s/clientset/versioned/typed/api/v1beta1/api_client.go)
- [clients/k8s/clientset/versioned/typed/api/v1beta1/sandbox.go](clients/k8s/clientset/versioned/typed/api/v1beta1/sandbox.go)
- [clients/k8s/clientset/versioned/typed/api/v1beta1/generated_expansion.go](clients/k8s/clientset/versioned/typed/api/v1beta1/generated_expansion.go)
- [clients/k8s/clientset/versioned/scheme/register.go](clients/k8s/clientset/versioned/scheme/register.go)
- [clients/k8s/clientset/versioned/fake/clientset_generated.go](clients/k8s/clientset/versioned/fake/clientset_generated.go)
- [clients/k8s/extensions/clientset/versioned/clientset.go](clients/k8s/extensions/clientset/versioned/clientset.go)
- [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/api_client.go](clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/api_client.go)
- [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/sandboxclaim.go](clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/sandboxclaim.go)
- [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/generated_expansion.go](clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/generated_expansion.go)
- [clients/k8s/extensions/clientset/versioned/fake/clientset_generated.go](clients/k8s/extensions/clientset/versioned/fake/clientset_generated.go)
- [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_api_client.go](clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_api_client.go)
- [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_sandboxclaim.go](clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_sandboxclaim.go)
- [clients/k8s/informers/externalversions/factory.go](clients/k8s/informers/externalversions/factory.go)
- [clients/k8s/informers/externalversions/generic.go](clients/k8s/informers/externalversions/generic.go)
- [clients/k8s/informers/externalversions/api/interface.go](clients/k8s/informers/externalversions/api/interface.go)
- [clients/k8s/informers/externalversions/api/v1beta1/interface.go](clients/k8s/informers/externalversions/api/v1beta1/interface.go)
- [clients/k8s/informers/externalversions/api/v1beta1/sandbox.go](clients/k8s/informers/externalversions/api/v1beta1/sandbox.go)
- [clients/k8s/informers/externalversions/internalinterfaces/factory_interfaces.go](clients/k8s/informers/externalversions/internalinterfaces/factory_interfaces.go)
- [clients/k8s/extensions/informers/externalversions/generic.go](clients/k8s/extensions/informers/externalversions/generic.go)
- [clients/k8s/extensions/informers/externalversions/api/v1beta1/interface.go](clients/k8s/extensions/informers/externalversions/api/v1beta1/interface.go)
- [clients/k8s/listers/api/v1beta1/sandbox.go](clients/k8s/listers/api/v1beta1/sandbox.go)
- [clients/k8s/listers/api/v1beta1/expansion_generated.go](clients/k8s/listers/api/v1beta1/expansion_generated.go)
- [clients/k8s/extensions/listers/api/v1beta1/sandboxclaim.go](clients/k8s/extensions/listers/api/v1beta1/sandboxclaim.go)
- [clients/k8s/extensions/listers/api/v1beta1/expansion_generated.go](clients/k8s/extensions/listers/api/v1beta1/expansion_generated.go)
- [api/v1beta1/sandbox_types.go](api/v1beta1/sandbox_types.go)
- [api/v1beta1/groupversion_info.go](api/v1beta1/groupversion_info.go)
- [extensions/api/v1beta1/sandboxclaim_types.go](extensions/api/v1beta1/sandboxclaim_types.go)
- [extensions/api/v1beta1/groupversion_info.go](extensions/api/v1beta1/groupversion_info.go)
- [clients/go/sandbox/k8s.go](clients/go/sandbox/k8s.go)
- [clients/go/sandbox/sandbox_test.go](clients/go/sandbox/sandbox_test.go)
</details>

# Generated Go Clientsets, Informers & Listers

The `clients/k8s` tree contains the `k8s.io/client-go`-style generated machinery used to interact with this project's Custom Resources from Go. It is produced by the `k8s.io/code-generator` tools (`client-gen`, `lister-gen`, `informer-gen`) and follows conventions that any consumer of `client-go` will already recognise: typed clientsets per group/version, shared informer factories with per-resource informers, and listers backed by an informer's indexer. Two parallel hierarchies are emitted — one for the core `agents.x-k8s.io` group (`Sandbox`) and a separate one for the `extensions.agents.x-k8s.io` group (`SandboxClaim`, `SandboxTemplate`, `SandboxWarmPool`) — because each lives in its own Go API package and gets its own clientset, scheme, listers, informers, and fake.

This page maps the generated layout onto the source: where each layer lives, how the typed REST shims are assembled, how the shared informer factory wires informers to listers, and how everything is regenerated. The wrapper SDK in `clients/go/sandbox` consumes these types directly and is used as the canonical example of how the layers fit together.

Sources: [codegen.go:19-28](), [dev/tools/client-gen-go.sh:24-78]()

## Where the generated tree comes from

A single shell script, `dev/tools/client-gen-go.sh`, drives all three code generators against the two API packages and writes everything into `clients/k8s/{clientset,listers,informers}` and `clients/k8s/extensions/{clientset,listers,informers}`. It is invoked through the top-level `//go:generate ./dev/tools/client-gen-go.sh` directive in `codegen.go`, so `go generate ./...` is the only entry point a developer needs.

```bash
# dev/tools/client-gen-go.sh:24-48
CMD="go run -modfile=tools.mod k8s.io/code-generator"
API_PKG="sigs.k8s.io/agent-sandbox/api/v1beta1"
CLIENT_PKG="sigs.k8s.io/agent-sandbox/clients/k8s"

${CMD}/cmd/client-gen \
  --output-dir "clients/k8s/clientset" \
  --output-pkg "${CLIENT_PKG}/clientset" \
  --clientset-name "versioned" \
  --input-base "" \
  --input "${API_PKG}"

${CMD}/cmd/lister-gen   --output-dir "clients/k8s/listers"   …  "${API_PKG}"
${CMD}/cmd/informer-gen --output-dir "clients/k8s/informers" \
  --versioned-clientset-package "${CLIENT_PKG}/clientset/versioned" \
  --listers-package "${CLIENT_PKG}/listers" \
  …  "${API_PKG}"
```

The same three invocations are then repeated with `EXT_API_PKG=sigs.k8s.io/agent-sandbox/extensions/api/v1beta1` and `EXT_CLIENT_PKG=…/clients/k8s/extensions`, which is why the extensions tree is a structurally identical sibling rather than additional group/version folders under the core clientset. Finally, `dev/tools/fix-boilerplate` re-applies the project's Apache-2.0 license header to every generated file.

Resources are discovered by the generators from `+genclient` markers on the API types — `Sandbox` (`api/v1beta1/sandbox_types.go:224`) for the core group; `SandboxClaim`, `SandboxTemplate`, and `SandboxWarmPool` (`extensions/api/v1beta1/sandboxclaim_types.go:175`, etc.) for extensions — combined with each package's `SchemeGroupVersion` (`api/v1beta1/groupversion_info.go:27-29`, `extensions/api/v1beta1/groupversion_info.go:27-29`).

Sources: [codegen.go:28](), [dev/tools/client-gen-go.sh:24-78](), [api/v1beta1/sandbox_types.go:224-244](), [api/v1beta1/groupversion_info.go:27-40](), [extensions/api/v1beta1/groupversion_info.go:27-40]()

## Layout at a glance

The two trees mirror the upstream `kubernetes/sample-controller` layout. The `clientset` builds typed REST clients; `informers` builds a `SharedInformerFactory` over those clients; `listers` reads from each informer's `cache.Indexer`.

```text
clients/k8s/                                    clients/k8s/extensions/
├── clientset/versioned/                        ├── clientset/versioned/
│   ├── clientset.go                            │   ├── clientset.go
│   ├── scheme/register.go                      │   ├── scheme/register.go
│   ├── fake/clientset_generated.go             │   ├── fake/clientset_generated.go
│   └── typed/api/v1beta1/                      │   └── typed/api/v1beta1/
│       ├── api_client.go  (AgentsV1beta1)      │       ├── api_client.go  (ExtensionsV1beta1)
│       ├── sandbox.go                          │       ├── sandboxclaim.go
│       ├── generated_expansion.go              │       ├── sandboxtemplate.go
│       └── fake/                               │       ├── sandboxwarmpool.go
│           ├── fake_api_client.go              │       ├── generated_expansion.go
│           └── fake_sandbox.go                 │       └── fake/{fake_api_client,fake_*}.go
├── informers/externalversions/                 ├── informers/externalversions/
│   ├── factory.go                              │   ├── factory.go
│   ├── generic.go                              │   ├── generic.go
│   ├── internalinterfaces/factory_interfaces.go│   ├── internalinterfaces/factory_interfaces.go
│   └── api/{interface.go, v1beta1/...}         │   └── api/{interface.go, v1beta1/...}
└── listers/api/v1beta1/                        └── listers/api/v1beta1/
    ├── sandbox.go                                  ├── sandboxclaim.go
    └── expansion_generated.go                      ├── sandboxtemplate.go
                                                    ├── sandboxwarmpool.go
                                                    └── expansion_generated.go
```

The following table summarises which generator produces which file pattern:

| Generator      | Output root                              | Representative files                                                          | Purpose                                                       |
| -------------- | ---------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `client-gen`   | `clientset/versioned/`                   | `clientset.go`, `typed/api/v1beta1/api_client.go`, `typed/api/v1beta1/*.go`   | Typed CRUD + `Watch` over REST per group/version              |
| `client-gen`   | `clientset/versioned/scheme/`            | `register.go`                                                                 | Runtime `Scheme` + `Codecs` + `ParameterCodec` for the group  |
| `client-gen`   | `clientset/versioned/fake/`              | `clientset_generated.go`, `typed/api/v1beta1/fake/*`                          | In-memory clientset backed by `client-go/testing.ObjectTracker` |
| `lister-gen`   | `listers/api/v1beta1/`                   | `sandbox.go`, `sandboxclaim.go`, `expansion_generated.go`                     | Indexer-backed cluster + namespaced read helpers              |
| `informer-gen` | `informers/externalversions/`            | `factory.go`, `generic.go`, `api/v1beta1/sandbox.go`                          | `SharedInformerFactory` + per-resource informers              |

Sources: [dev/tools/client-gen-go.sh:24-78](), [clients/k8s/clientset/versioned/clientset.go:15](), [clients/k8s/informers/externalversions/factory.go:15](), [clients/k8s/listers/api/v1beta1/sandbox.go:15]()

## Clientset and typed clients

`Clientset` is the per-tree aggregate that consumers usually hold. It embeds a `DiscoveryClient` and exposes one method per group/version. For the core tree there is exactly one such method, `AgentsV1beta1()`; for the extensions tree it is `ExtensionsV1beta1()`. Both are also defined as an `Interface` so the matching fake clientset can satisfy the same contract.

```go
// clients/k8s/clientset/versioned/clientset.go:29-43
type Interface interface {
    Discovery() discovery.DiscoveryInterface
    AgentsV1beta1() agentsv1beta1.AgentsV1beta1Interface
}

type Clientset struct {
    *discovery.DiscoveryClient
    agentsV1beta1 *agentsv1beta1.AgentsV1beta1Client
}

func (c *Clientset) AgentsV1beta1() agentsv1beta1.AgentsV1beta1Interface { return c.agentsV1beta1 }
```

The three constructors are also identical to the upstream conventions: `NewForConfig`, `NewForConfigAndClient`, and `NewForConfigOrDie`. `NewForConfig` calls `rest.HTTPClientFor` then delegates to `NewForConfigAndClient` (`clients/k8s/clientset/versioned/clientset.go:58-99`); when both group clientsets are needed at once the `httpClient` is shared so a single transport is reused. The extensions clientset has the same shape but exposes `SandboxClaims`, `SandboxTemplates`, and `SandboxWarmPools` getters on its `ExtensionsV1beta1Client` (`clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/api_client.go:27-49`).

Per group/version the typed sub-client is configured against `SchemeGroupVersion` and a per-group `scheme.ParameterCodec`:

```go
// clients/k8s/clientset/versioned/typed/api/v1beta1/api_client.go:81-90
func setConfigDefaults(config *rest.Config) {
    gv := apiv1beta1.SchemeGroupVersion
    config.GroupVersion = &gv
    config.APIPath = "/apis"
    config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion()
    if config.UserAgent == "" {
        config.UserAgent = rest.DefaultKubernetesUserAgent()
    }
}
```

Each resource gets a `gentype.ClientWithList`-backed implementation that supplies CRUD, `UpdateStatus`, `DeleteCollection`, `Watch`, and `Patch`:

```go
// clients/k8s/clientset/versioned/typed/api/v1beta1/sandbox.go:37-68
type SandboxInterface interface {
    Create(ctx, sandbox, opts) (*Sandbox, error)
    Update(ctx, sandbox, opts) (*Sandbox, error)
    UpdateStatus(ctx, sandbox, opts) (*Sandbox, error)
    Delete(ctx, name, opts) error
    DeleteCollection(ctx, opts, listOpts) error
    Get(ctx, name, opts) (*Sandbox, error)
    List(ctx, opts) (*SandboxList, error)
    Watch(ctx, opts) (watch.Interface, error)
    Patch(ctx, name, pt, data, opts, subresources...) (*Sandbox, error)
    SandboxExpansion
}

type sandboxes struct { *gentype.ClientWithList[*apiv1beta1.Sandbox, *apiv1beta1.SandboxList] }

func newSandboxes(c *AgentsV1beta1Client, namespace string) *sandboxes {
    return &sandboxes{ gentype.NewClientWithList[*Sandbox, *SandboxList](
        "sandboxes", c.RESTClient(), scheme.ParameterCodec, namespace,
        func() *Sandbox { return &Sandbox{} },
        func() *SandboxList { return &SandboxList{} },
    )}
}
```

The presence of `UpdateStatus` is driven by the `+genclient` marker without `noStatus`; the file's own doc comment notes this (`clients/k8s/clientset/versioned/typed/api/v1beta1/sandbox.go:40-41`). The `SandboxExpansion` interface in `generated_expansion.go` is empty — it exists so a hand-written file may add methods to `SandboxInterface` without conflicting with regenerated code.

Sources: [clients/k8s/clientset/versioned/clientset.go:29-99](), [clients/k8s/clientset/versioned/typed/api/v1beta1/api_client.go:27-99](), [clients/k8s/clientset/versioned/typed/api/v1beta1/sandbox.go:30-68](), [clients/k8s/clientset/versioned/typed/api/v1beta1/generated_expansion.go:19](), [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/api_client.go:27-49]()

## Layered architecture

```mermaid
flowchart TB
    subgraph apis["API packages (+genclient sources)"]
        coreAPI["api/v1beta1<br/>Sandbox<br/>(group: agents.x-k8s.io)"]
        extAPI["extensions/api/v1beta1<br/>SandboxClaim / SandboxTemplate / SandboxWarmPool<br/>(group: extensions.agents.x-k8s.io)"]
    end

    subgraph clientset["clientset/versioned (client-gen)"]
        coreCS["Clientset<br/>AgentsV1beta1()"]
        coreTyped["AgentsV1beta1Client<br/>Sandboxes(ns)"]
        coreFake["fake.Clientset<br/>NewSimpleClientset"]
        extCS["Clientset<br/>ExtensionsV1beta1()"]
        extTyped["ExtensionsV1beta1Client<br/>SandboxClaims/Templates/WarmPools"]
        extFake["fake.Clientset<br/>NewSimpleClientset"]
        scheme["scheme.Scheme<br/>scheme.Codecs<br/>ParameterCodec"]
    end

    subgraph informers["informers/externalversions (informer-gen)"]
        factory["sharedInformerFactory<br/>InformerFor / ForResource / Start"]
        groupIface["api.Interface<br/>V1beta1()"]
        sandboxInf["SandboxInformer<br/>Informer() / Lister()"]
    end

    subgraph listers["listers/api/v1beta1 (lister-gen)"]
        sandboxLister["SandboxLister<br/>(read-only over cache.Indexer)"]
        extListers["SandboxClaim/Template/WarmPool Listers"]
    end

    consumer["clients/go/sandbox.K8sHelper"]

    coreAPI -->|+genclient| coreTyped
    extAPI  -->|+genclient| extTyped
    coreTyped --> coreCS
    extTyped  --> extCS
    coreCS -. interface .- coreFake
    extCS  -. interface .- extFake
    scheme --> coreTyped
    scheme --> extTyped

    coreCS --> factory
    factory --> groupIface --> sandboxInf
    sandboxInf -- "cache.Indexer" --> sandboxLister
    factory -- "ForResource(GVR)" --> extListers

    consumer -->|AgentsV1beta1Interface| coreTyped
    consumer -->|ExtensionsV1beta1Interface| extTyped
```

The diagram captures the dependency direction enforced by the generators: API packages are the only hand-written input; the clientset depends on them and on the local `scheme`; informers depend on the versioned clientset; listers depend on the informer's `cache.Indexer`; consumers depend on the typed group interfaces. The extensions side is a structural copy of the core side, swapped onto the extensions API package.

Sources: [clients/k8s/clientset/versioned/clientset.go:29-43](), [clients/k8s/informers/externalversions/factory.go:36-54](), [clients/k8s/informers/externalversions/api/v1beta1/sandbox.go:33-100](), [clients/k8s/listers/api/v1beta1/sandbox.go:26-69](), [clients/go/sandbox/k8s.go:50-121]()

## Scheme registration

Each clientset ships its own `scheme` subpackage. It builds a private `runtime.Scheme`, registers the API package's `AddToScheme`, and exposes `Codecs`, `ParameterCodec`, and `AddToScheme` for composition with other schemes (for example `clientsetscheme.Scheme`).

```go
// clients/k8s/clientset/versioned/scheme/register.go:28-54
var Scheme = runtime.NewScheme()
var Codecs = serializer.NewCodecFactory(Scheme)
var ParameterCodec = runtime.NewParameterCodec(Scheme)
var localSchemeBuilder = runtime.SchemeBuilder{ agentsv1beta1.AddToScheme }
var AddToScheme = localSchemeBuilder.AddToScheme

func init() {
    v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
    utilruntime.Must(AddToScheme(Scheme))
}
```

Because the core and extensions trees are separate clientsets, they each have a separate scheme package. Consumers that need to decode both groups through the same codecs must call both `AddToScheme` functions against a shared scheme.

Sources: [clients/k8s/clientset/versioned/scheme/register.go:28-54]()

## Fake clientsets

`client-gen` emits a parallel `fake/` package per tree. `NewSimpleClientset(objects...)` returns a `*Clientset` that satisfies the same `versioned.Interface` as the real clientset but is backed by `k8s.io/client-go/testing.NewObjectTracker` and a default reactor pipeline. A watch reactor is also pre-registered against the tracker so any `Watch()` call against the fake returns events generated by tracker mutations.

```go
// clients/k8s/extensions/clientset/versioned/fake/clientset_generated.go:39-65
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
    o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
    for _, obj := range objects { _ = o.Add(obj) }
    cs := &Clientset{tracker: o}
    cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
    cs.AddReactor("*", "*", testing.ObjectReaction(o))
    cs.AddWatchReactor("*", func(action testing.Action) (bool, watch.Interface, error) { … })
    return cs
}
```

The fake group client mirrors the real one method-for-method, returning per-resource `gentype.FakeClientWithList`-backed fakes:

```go
// clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_api_client.go:25-39
type FakeExtensionsV1beta1 struct { *testing.Fake }
func (c *FakeExtensionsV1beta1) SandboxClaims(ns string) v1beta1.SandboxClaimInterface     { return newFakeSandboxClaims(c, ns) }
func (c *FakeExtensionsV1beta1) SandboxTemplates(ns string) v1beta1.SandboxTemplateInterface { return newFakeSandboxTemplates(c, ns) }
func (c *FakeExtensionsV1beta1) SandboxWarmPools(ns string) v1beta1.SandboxWarmPoolInterface { return newFakeSandboxWarmPools(c, ns) }

// clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_sandboxclaim.go:31-50
func newFakeSandboxClaims(fake *FakeExtensionsV1beta1, namespace string) apiv1beta1.SandboxClaimInterface {
    return &fakeSandboxClaims{ gentype.NewFakeClientWithList[*SandboxClaim, *SandboxClaimList](
        fake.Fake, namespace,
        v1beta1.SchemeGroupVersion.WithResource("sandboxclaims"),
        v1beta1.SchemeGroupVersion.WithKind("SandboxClaim"),
        func() *SandboxClaim     { return &SandboxClaim{} },
        func() *SandboxClaimList { return &SandboxClaimList{} },
        func(dst, src *SandboxClaimList) { dst.ListMeta = src.ListMeta },
        func(list *SandboxClaimList) []*SandboxClaim     { return gentype.ToPointerSlice(list.Items) },
        func(list *SandboxClaimList, items []*SandboxClaim) { list.Items = gentype.FromPointerSlice(items) },
    ), fake }
}
```

`NewSimpleClientset` is marked `Deprecated` upstream in favour of the apply-aware `NewClientset` constructor that becomes available once `--with-applyconfig` is used; the call sites in `clients/go/sandbox` suppress the deprecation with a `//nolint:staticcheck` and an inline TODO to regenerate with that flag (`clients/go/sandbox/sandbox_test.go:65-67`). The fake clientset additionally returns `true` from `IsWatchListSemanticsUnSupported()` to signal to reflectors that `WatchList` is not implemented (`clients/k8s/clientset/versioned/fake/clientset_generated.go:84-93`).

Sources: [clients/k8s/clientset/versioned/fake/clientset_generated.go:39-103](), [clients/k8s/extensions/clientset/versioned/fake/clientset_generated.go:39-103](), [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_api_client.go:25-46](), [clients/k8s/extensions/clientset/versioned/typed/api/v1beta1/fake/fake_sandboxclaim.go:25-50](), [clients/go/sandbox/sandbox_test.go:42-45]()

## Shared informer factory

`informer-gen` emits a `SharedInformerFactory` per tree. The factory is a tiny `sync.Mutex`-guarded registry over `reflect.Type → cache.SharedIndexInformer`, keyed by the object type each informer is configured for. Options are supplied with the standard `WithCustomResyncConfig`, `WithTweakListOptions`, `WithNamespace`, and `WithTransform` functional options.

```go
// clients/k8s/informers/externalversions/factory.go:36-54
type sharedInformerFactory struct {
    client           versioned.Interface
    namespace        string
    tweakListOptions internalinterfaces.TweakListOptionsFunc
    lock             sync.Mutex
    defaultResync    time.Duration
    customResync     map[reflect.Type]time.Duration
    transform        cache.TransformFunc
    informers        map[reflect.Type]cache.SharedIndexInformer
    startedInformers map[reflect.Type]bool
    wg               sync.WaitGroup
    shuttingDown     bool
}
```

`InformerFor` is the deduplicating heart of the factory: it looks up an existing informer by `reflect.TypeOf(obj)` and, if absent, invokes the `NewInformerFunc` supplied by the per-resource accessor and applies the configured transform.

```go
// clients/k8s/informers/externalversions/factory.go:179-199
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
    f.lock.Lock(); defer f.lock.Unlock()
    informerType := reflect.TypeOf(obj)
    if informer, exists := f.informers[informerType]; exists { return informer }
    resyncPeriod, exists := f.customResync[informerType]
    if !exists { resyncPeriod = f.defaultResync }
    informer = newFunc(f.client, resyncPeriod)
    informer.SetTransform(f.transform)
    f.informers[informerType] = informer
    return informer
}
```

`Start` walks `f.informers`, launches `informer.Run(stopCh)` in a goroutine for each one that has not been started yet, and records it in `startedInformers` so a second `Start` call is a no-op for already-running informers; `WaitForCacheSync` filters by the same set when blocking on `HasSynced` (`clients/k8s/informers/externalversions/factory.go:123-175`). `Shutdown` flips `shuttingDown` and blocks on `f.wg` until all the goroutines started by previous `Start` calls return.

Group navigation is structured as `Factory → Group → Version → Resource`. Each layer is generated; the leaf returns a `SandboxInformer` (or per-extensions-resource equivalent):

```go
// clients/k8s/informers/externalversions/factory.go:259-261
func (f *sharedInformerFactory) Agents() api.Interface { return api.New(f, f.namespace, f.tweakListOptions) }

// clients/k8s/informers/externalversions/api/interface.go:24-44
type Interface interface { V1beta1() v1beta1.Interface }
func (g *group) V1beta1() v1beta1.Interface { return v1beta1.New(g.factory, g.namespace, g.tweakListOptions) }

// clients/k8s/informers/externalversions/api/v1beta1/interface.go:24-43
type Interface interface { Sandboxes() SandboxInformer }
func (v *version) Sandboxes() SandboxInformer {
    return &sandboxInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
```

The factory also implements `ForResource(GVR)` for callers that only know a `schema.GroupVersionResource`. It is a switch over the resources discovered for the tree:

```go
// clients/k8s/informers/externalversions/generic.go:51-60
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
    switch resource {
    case v1beta1.SchemeGroupVersion.WithResource("sandboxes"):
        return &genericInformer{ resource: resource.GroupResource(),
            informer: f.Agents().V1beta1().Sandboxes().Informer() }, nil
    }
    return nil, fmt.Errorf("no informer found for %v", resource)
}
```

The extensions tree emits the same switch with three cases — `sandboxclaims`, `sandboxtemplates`, `sandboxwarmpools` — each routed through `f.Extensions().V1beta1()` (`clients/k8s/extensions/informers/externalversions/generic.go:51-64`, `clients/k8s/extensions/informers/externalversions/api/v1beta1/interface.go:24-57`).

Sources: [clients/k8s/informers/externalversions/factory.go:36-261](), [clients/k8s/informers/externalversions/generic.go:29-60](), [clients/k8s/informers/externalversions/api/interface.go:24-44](), [clients/k8s/informers/externalversions/api/v1beta1/interface.go:24-43](), [clients/k8s/extensions/informers/externalversions/api/v1beta1/interface.go:24-57]()

## Per-resource informer wiring

The leaf informer file (e.g. `sandbox.go`) is where the typed clientset is finally joined to a `cache.SharedIndexInformer`. `NewFilteredSandboxInformer` builds a `cache.ListWatch` whose `ListFunc`/`WatchFunc` (and their `WithContext` siblings) delegate to the typed `AgentsV1beta1().Sandboxes(namespace)` sub-client, optionally rewriting `metav1.ListOptions` via `tweakListOptions`.

```go
// clients/k8s/informers/externalversions/api/v1beta1/sandbox.go:56-100
func NewFilteredSandboxInformer(client versioned.Interface, namespace string,
    resyncPeriod time.Duration, indexers cache.Indexers,
    tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
    return cache.NewSharedIndexInformer(
        cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{
            ListFunc:  func(o v1.ListOptions) (runtime.Object, error)   { /* tweak; client.AgentsV1beta1().Sandboxes(namespace).List(...) */ },
            WatchFunc: func(o v1.ListOptions) (watch.Interface, error)  { /* tweak; ...Watch(...) */ },
            ListWithContextFunc:  /* ctx-aware List  */,
            WatchFuncWithContext: /* ctx-aware Watch */,
        }, client),
        &agentsandboxapiv1beta1.Sandbox{}, resyncPeriod, indexers,
    )
}

func (f *sandboxInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
    return NewFilteredSandboxInformer(client, f.namespace, resyncPeriod,
        cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}

func (f *sandboxInformer) Informer() cache.SharedIndexInformer { return f.factory.InformerFor(&Sandbox{}, f.defaultInformer) }
func (f *sandboxInformer) Lister()   apiv1beta1.SandboxLister  { return apiv1beta1.NewSandboxLister(f.Informer().GetIndexer()) }
```

A few details worth highlighting:

- The default informer always installs `cache.NamespaceIndex → cache.MetaNamespaceIndexFunc`, which is what makes `Lister().Sandboxes(namespace)` efficient.
- `cache.ToListWatcherWithWatchListSemantics(..., client)` opts the informer into the streaming `WatchList` semantics where supported by the API server — the fake clientset signals it is *not* supported through `IsWatchListSemanticsUnSupported() bool` on `Clientset` (`clients/k8s/clientset/versioned/fake/clientset_generated.go:91-93`).
- The `NewInformerFunc` signature `func(versioned.Interface, time.Duration) cache.SharedIndexInformer` is defined in `internalinterfaces` to keep the factory free of import cycles (`clients/k8s/informers/externalversions/internalinterfaces/factory_interfaces.go:28-39`).

Sources: [clients/k8s/informers/externalversions/api/v1beta1/sandbox.go:33-100](), [clients/k8s/informers/externalversions/internalinterfaces/factory_interfaces.go:28-39](), [clients/k8s/clientset/versioned/fake/clientset_generated.go:91-93]()

## Listers

Listers are tiny wrappers around `k8s.io/client-go/listers.ResourceIndexer[T]`. They expose namespaced and cluster-scoped reads over the informer's `cache.Indexer`:

```go
// clients/k8s/listers/api/v1beta1/sandbox.go:38-69
type sandboxLister struct {
    listers.ResourceIndexer[*apiv1beta1.Sandbox]
}
func NewSandboxLister(indexer cache.Indexer) SandboxLister {
    return &sandboxLister{listers.New[*apiv1beta1.Sandbox](indexer, apiv1beta1.Resource("sandbox"))}
}
func (s *sandboxLister) Sandboxes(namespace string) SandboxNamespaceLister {
    return sandboxNamespaceLister{listers.NewNamespaced[*apiv1beta1.Sandbox](s.ResourceIndexer, namespace)}
}
```

All returned objects are documented as read-only — callers must `DeepCopy` before mutation. `expansion_generated.go` emits empty marker interfaces (`SandboxListerExpansion`, `SandboxNamespaceListerExpansion`, and one pair per extensions resource) so hand-written methods can be added to a lister without conflicting with regenerated code. The extensions tree mirrors this with one file per resource (`sandboxclaim.go`, `sandboxtemplate.go`, `sandboxwarmpool.go`).

Sources: [clients/k8s/listers/api/v1beta1/sandbox.go:26-69](), [clients/k8s/listers/api/v1beta1/expansion_generated.go:19-26](), [clients/k8s/extensions/listers/api/v1beta1/sandboxclaim.go:26-69](), [clients/k8s/extensions/listers/api/v1beta1/expansion_generated.go:19-41]()

## Wiring it together

The wrapper SDK shows the canonical end-to-end consumption pattern: build a shared `*http.Client`, hand it to both `versioned.NewForConfigAndClient` calls, and keep only the per-group interfaces around for downstream code.

```go
// clients/go/sandbox/k8s.go:82-121
httpClient, err := rest.HTTPClientFor(config)
…
agentsCS, err := agentsclientset.NewForConfigAndClient(config, httpClient)
…
extensionsCS, err := extensionsclientset.NewForConfigAndClient(config, httpClient)
…
return &K8sHelper{
    AgentsClient:     agentsCS.AgentsV1beta1(),
    ExtensionsClient: extensionsCS.ExtensionsV1beta1(),
    …
}, nil
```

Calls then go directly through the typed interface, for example creating and watching a `SandboxClaim`:

```go
// clients/go/sandbox/k8s.go:148-208
created, err := h.ExtensionsClient.SandboxClaims(namespace).Create(ctx, claim, metav1.CreateOptions{})
…
watcher, err := h.ExtensionsClient.SandboxClaims(namespace).Watch(ctx, listOpts)
```

Tests follow the same pattern but swap in the fake clientsets without touching the rest of the code — that interface-symmetry is the whole point of generating both real and fake implementations together:

```go
// clients/go/sandbox/sandbox_test.go:65-72
agentsCS := fakeagents.NewSimpleClientset()
extensionsCS := fakeextensions.NewSimpleClientset()
opts.K8sHelper = &K8sHelper{
    AgentsClient:     agentsCS.AgentsV1beta1(),
    ExtensionsClient: extensionsCS.ExtensionsV1beta1(),
    Log:              opts.Logger,
}
```

Sources: [clients/go/sandbox/k8s.go:36-41](), [clients/go/sandbox/k8s.go:50-121](), [clients/go/sandbox/k8s.go:148-208](), [clients/go/sandbox/sandbox_test.go:42-72]()

## Regenerating

To regenerate everything, run `go generate ./...` from the repository root; the `//go:generate` directive at `codegen.go:28` invokes `dev/tools/client-gen-go.sh`, which produces the entire `clients/k8s/{clientset,informers,listers}` and `clients/k8s/extensions/{clientset,informers,listers}` trees in place and then re-applies the project's Apache-2.0 license headers. The generators emit `// Code generated by client-gen. DO NOT EDIT.` (and equivalents for `informer-gen` / `lister-gen`) on every file, so the only sanctioned way to add custom behaviour without losing it on regeneration is via the empty `*Expansion` interfaces emitted alongside each resource. Adding a new CR is correspondingly a matter of adding `+genclient` to the type and re-running generation — no edits to the clientset, factory, lister, or fake plumbing are required.

Sources: [codegen.go:19-28](), [dev/tools/client-gen-go.sh:55-78](), [clients/k8s/clientset/versioned/typed/api/v1beta1/generated_expansion.go:15-19]()
