Context
ADR #63 (agent-deployment-templates, Accepted) already scoped slice 3 = K8s driver as the second provider behind the same RuntimeDriver-shaped seam as ECS (slice 2, PR #65). This issue tracks that slice, broken into independently-shippable sub-slices, per Brett's ask (orbstack-local k8s cluster as a Studio-managed target).
Full deployment scope confirmed: compose → bundle → k8s apply, on par with ECS provision — not a read-only/observe-first MVP.
Relevant ADRs: deployment-control-plane (ADR-2, §6-state ⇄ k8s mapping), agent-lifecycle (ADR-1), runtime-identity-context (ADR-19, FleetBinding k8s fields), agent-deployment-templates (#63).
Correction after re-reading the code (self-review, 2026-08-21): the k8s schema shape is not net-new. manifest.rs has carried Runtime::Kubernetes(KubernetesRuntime { node_selector, service_account, tolerations }) since slice-0 (c17bf7d, both here and in the upstream openab/operator source) — it's currently hard-rejected by two explicit guards: apply.rs:239-244 and create.rs:56-58 (bail!("Kubernetes runtime not yet implemented")). 3b's job is to implement behind this existing enum arm and delete those two guards, not design new schema. KubernetesRuntime has no context/namespace/cluster field — confirms the original intent was for cluster targeting to live at the FleetBinding level (ADR-19), not per-manifest; carry that assumption forward unless there's a reason to override per-agent.
Sub-slices
- 3a — Provisioning driver trait extraction (prerequisite, no ADR yet).
apply/scale/delete/get are inline aws_sdk_ecs calls with no trait boundary — ADR-2's "RuntimeDriver is the only layer with vendor terms" is only true for state classification (project() in agent-lifecycle) today, not CRUD. Extract a trait, EcsDriver becomes the first impl. Pure refactor, behavior unchanged, existing tests must stay green.
- Also in scope for 3a (missed in the first pass):
create.rs's interactive wizard is AWS-coupled starting at step 3 (Discord secret → Secrets Manager SmClient constructed unconditionally), before runtime is even asked at step 4 — VPC/subnet/security-group prompts (steps 6-8) are ECS-only concepts. The wizard needs restructuring so runtime selection happens early and branches: k8s path skips VPC/SG entirely and prompts kubeconfig context/namespace instead.
status.rs/studio_api.rs (Studio-local additions, not upstream) call ECS describe_* directly with no driver branching — the read-model needs a k8s-observe counterpart. This is separate from agent-lifecycle's RuntimeDriver::project() (that only classifies a native observation Studio already fetched; something has to fetch the k8s-native observation first).
- 3b — K8sDriver skeleton. kubeconfig context resolution (
principal_source/scope/location per ADR-19), apply → Deployment, observe → Pod list → 4-discriminator → 6-state (ADR-2's k8s mapping table + traps). Removes the two not yet implemented guards. Adds kube + k8s-openapi crate deps.
- 3c — Bundle carrier.
bundle_from (manifest.rs) currently only resolves to an S3 state prefix; add ConfigMap/volume (or init-container pull) carrier for k8s.
- 3d — Secrets. Corrected: not just a new URI scheme.
secrets.rs::resolve_value_from's output contract is the ECS-native valueFrom shape ({arn}:{key}::) — a k8s target needs a different output (a Secret key selector), so this needs a driver-parameterized result type (e.g. SecretRef::Ecs(String) | SecretRef::K8s{ name, key }), with k8s-secret://<name>#<key> as the new input shorthand alongside the existing aws-sm://.
- 3e — Identity context panel. Wire k8s fields (context user/SA, cluster/namespace,
kubectl auth whoami) into RuntimeContext per ADR-19's table.
- 3f — Console wiring.
FleetBinding gains a k8s driver option + kubeconfig context selector; orbstack's local cluster is just another context, no special-casing.
Explicit non-goals / open questions
ingress.rs (Cloud Map + VPC Link + API Gateway) stays ECS-only, permanently. manifest.validate() already enforces this: spec.ingress is rejected for non-ECS runtimes with "use native Kubernetes Ingress otherwise." Not a gap — a deliberate boundary from slice-0. k8s operators manage ingress with native k8s objects.
bootstrap.rs k8s equivalent (namespace/RBAC/ServiceAccount one-time setup) — open question, leaning out of scope for v1. ECS bootstrap provisions cluster/IAM/SG/S3/log-group account-wide; for orbstack (operator already has cluster-admin on their own local cluster) this is plausibly unnecessary — assume namespace/RBAC pre-exist as an operator prerequisite (kubectl create namespace oab), revisit only if a shared/multi-tenant k8s target shows up later.
Starting with 3a.
Context
ADR #63 (
agent-deployment-templates, Accepted) already scoped slice 3 = K8s driver as the second provider behind the sameRuntimeDriver-shaped seam as ECS (slice 2, PR #65). This issue tracks that slice, broken into independently-shippable sub-slices, per Brett's ask (orbstack-local k8s cluster as a Studio-managed target).Full deployment scope confirmed: compose → bundle → k8s apply, on par with ECS provision — not a read-only/observe-first MVP.
Relevant ADRs:
deployment-control-plane(ADR-2, §6-state ⇄ k8s mapping),agent-lifecycle(ADR-1),runtime-identity-context(ADR-19, FleetBinding k8s fields),agent-deployment-templates(#63).Correction after re-reading the code (self-review, 2026-08-21): the k8s schema shape is not net-new.
manifest.rshas carriedRuntime::Kubernetes(KubernetesRuntime { node_selector, service_account, tolerations })since slice-0 (c17bf7d, both here and in the upstreamopenab/operatorsource) — it's currently hard-rejected by two explicit guards:apply.rs:239-244andcreate.rs:56-58(bail!("Kubernetes runtime not yet implemented")). 3b's job is to implement behind this existing enum arm and delete those two guards, not design new schema.KubernetesRuntimehas nocontext/namespace/clusterfield — confirms the original intent was for cluster targeting to live at the FleetBinding level (ADR-19), not per-manifest; carry that assumption forward unless there's a reason to override per-agent.Sub-slices
apply/scale/delete/getare inlineaws_sdk_ecscalls with no trait boundary — ADR-2's "RuntimeDriver is the only layer with vendor terms" is only true for state classification (project()inagent-lifecycle) today, not CRUD. Extract a trait,EcsDriverbecomes the first impl. Pure refactor, behavior unchanged, existing tests must stay green.create.rs's interactive wizard is AWS-coupled starting at step 3 (Discord secret → Secrets ManagerSmClientconstructed unconditionally), before runtime is even asked at step 4 — VPC/subnet/security-group prompts (steps 6-8) are ECS-only concepts. The wizard needs restructuring so runtime selection happens early and branches: k8s path skips VPC/SG entirely and prompts kubeconfig context/namespace instead.status.rs/studio_api.rs(Studio-local additions, not upstream) call ECSdescribe_*directly with no driver branching — the read-model needs a k8s-observe counterpart. This is separate fromagent-lifecycle'sRuntimeDriver::project()(that only classifies a native observation Studio already fetched; something has to fetch the k8s-native observation first).principal_source/scope/locationper ADR-19),apply→ Deployment,observe→ Pod list → 4-discriminator → 6-state (ADR-2's k8s mapping table + traps). Removes the twonot yet implementedguards. Addskube+k8s-openapicrate deps.bundle_from(manifest.rs) currently only resolves to an S3 state prefix; add ConfigMap/volume (or init-container pull) carrier for k8s.secrets.rs::resolve_value_from's output contract is the ECS-nativevalueFromshape ({arn}:{key}::) — a k8s target needs a different output (a Secret key selector), so this needs a driver-parameterized result type (e.g.SecretRef::Ecs(String) | SecretRef::K8s{ name, key }), withk8s-secret://<name>#<key>as the new input shorthand alongside the existingaws-sm://.kubectl auth whoami) intoRuntimeContextper ADR-19's table.FleetBindinggains a k8s driver option + kubeconfig context selector; orbstack's local cluster is just another context, no special-casing.Explicit non-goals / open questions
ingress.rs(Cloud Map + VPC Link + API Gateway) stays ECS-only, permanently.manifest.validate()already enforces this:spec.ingressis rejected for non-ECS runtimes with "use native Kubernetes Ingress otherwise." Not a gap — a deliberate boundary from slice-0. k8s operators manage ingress with native k8s objects.bootstrap.rsk8s equivalent (namespace/RBAC/ServiceAccount one-time setup) — open question, leaning out of scope for v1. ECS bootstrap provisions cluster/IAM/SG/S3/log-group account-wide; for orbstack (operator already has cluster-admin on their own local cluster) this is plausibly unnecessary — assume namespace/RBAC pre-exist as an operator prerequisite (kubectl create namespace oab), revisit only if a shared/multi-tenant k8s target shows up later.Starting with 3a.