Sandbox pools
SandboxPool and SandboxLease are alpha APIs. Set
Use agentSandbox.mode=managed for Kobe-owned management and child runtimes,
or external after installing Agent Sandbox v1.0.0 yourself. Kobe validates
consumed APIs before mounting the Sandbox HTTP API; disabled mounts no
Sandbox endpoints.
Administrators author SandboxPool objects. API callers only choose an allowed
pool, TTL, and optional alias; they cannot provide a Pod spec, namespace,
RuntimeClass, environment values, mounts, PVCs, or target credentials.
Runtime ownership
agentSandbox.mode has three values:
disabledis the default. Kobe renders no upstream Agent Sandbox resources and serves no new Sandbox API work. Lifecycle controllers continue cleanup for leases admitted before the mode changed.externalinstalls, upgrades, and uninstalls nothing. The operator owns the Agent Sandbox v1.0.0 core, extensions, CRDs, and controller.managedinstalls the chart-pinned v1.0.0 runtime by controller image digest, retains its CRDs, RBAC, and controller on Helm uninstall, and publishes a retained childBootstrapConfigfrom the same manifest.
Kobe checks that all four consumed CRDs are established, serve v1beta1 only
(no leftover v1alpha1 or conversion webhook), and expose the WarmPool
generation field. Validation is GET-only: it does not inspect one particular
Deployment topology and does not create a temporary SandboxClaim.
Consequently, API compatibility does not prove that the external controller is
healthy. The actual Claim for a requested lease still fails closed during
provisioning, and its administrator-declared readiness command must pass before
that lease becomes Ready.
Child placement requires managed mode. Reference the BootstrapConfig
published by the Helm release explicitly from the child ClusterPool. For a
release named kobe, the default reference is:
spec:
bootstraps:
- name: kobe-agent-sandbox-v1-0-0
Kobe checks the exact BootstrapConfig owner and pinned-manifest digest before allocating a child. After the generic ClusterInstance bootstrap completes, Kobe authenticates the installed CRDs and controller image, then runs a restart-safe create/delete canary inside that exact child. The canary checkpoints the Claim, Sandbox, and Pod UIDs and proves their absence before Kobe creates the caller's Claim. Helm contains no operational canary hook.
If a cluster still runs v0.5.x, follow the upstream v1.0.0 migration: prune
v1alpha1 from CRD status.storedVersions, install v1.0.0, then delete the
leftover webhook Service, cert Secret, and namespaced Role/RoleBinding.
Update operator-authored child bootstrap content at the same time. Managed
child pools must be recycled after changing the pinned runtime; Kobe rejects
mismatched children. Kobe never performs this upgrade.
Agent Sandbox does not install gVisor, Kata, node runtimes, or RuntimeClasses.
This release certifies only trusted-runc, for trusted workloads. The gvisor
and kata schema values are reserved for a future isolation qualification
under issue #14; selecting either keeps the pool Ready=False even when the
named RuntimeClass exists. Kobe never downgrades either tier silently to runc.
Example
apiVersion: kobe.kunobi.ninja/v1alpha1
kind: SandboxPool
metadata:
name: agent-small
namespace: kobe
spec:
warmCapacity: 2
defaultTtl: 30m
maxTtl: 2h
provisioningTimeout: 10m
placement:
type: management
template:
defaultContainer: workspace
containers:
- name: workspace
image: ghcr.io/example/agent-sandbox:v1
command: ["/usr/local/bin/agent"]
args: ["serve"]
resources:
requests:
cpu: 250m
memory: 512Mi
ephemeralStorage: 1Gi
limits:
cpu: "1"
memory: 2Gi
ephemeralStorage: 4Gi
exposedPorts:
- name: http
container: workspace
port: 8080
isolation:
tier: trusted-runc
readiness:
canary:
argv: ["/usr/local/bin/agent", "healthcheck"]
timeout: 30s
Every container must declare CPU, memory, and ephemeral-storage requests and
limits. defaultContainer and each exposed port must refer to a declared
container. Kobe runs every Sandbox container as UID/GID 65532, drops all
Linux capabilities, disables privilege escalation, and applies the
RuntimeDefault seccomp profile. Images must therefore work as that non-root
user and must not require writable root-owned paths. Canary argv is executed
directly, without an implicit shell.
Admission ledger namespace
Sandbox quota slots, aliases, and distributed operation gates are coordination
Lease objects. When Sandbox mode is enabled, the chart creates a dedicated
namespace named from the immutable Helm release identity
(<release>-sandbox-ledger-<hash>) with:
- a
ValidatingAdmissionPolicythat permits Lease mutations only from the exact Kobe operator ServiceAccount; - a namespace-wide Lease object quota, configured by
agentSandbox.reservationObjectLimit(default4096, minimum4); and - namespace-scoped operator RBAC. The chart grants tenants no access there, and the admission policy denies every non-operator Lease create, update, or delete even if another administrator grants broader RBAC.
The namespace, policy, binding, and quota are retained on Helm uninstall.
Before admission, Kobe creates one exact access gate per Sandbox; admission
then adds one quota token and, when requested, one alias token. Each principal
with active operations adds one shared principal ledger. This is why values
below the documented minimum cannot represent one fully usable aliased lease.
Deleting any of them while a SandboxLease is live can free its admission slot
or erase the proof that all API replicas drained before teardown. Remove
retained ledger resources only after every Sandbox lease, reservation, and
operation gate has been verified absent. The admission policy
requires Kubernetes 1.30 or newer with the ValidatingAdmissionPolicy and
ResourceQuota admission plugins enabled. Before serving Sandbox routes, Kobe
confirms its authenticated ServiceAccount identity and proves both plugins with
server-side dry-run requests that must be denied; startup fails if either
control is inactive.
The namespace is intentionally not configurable. Moving it would both reset admission capacity and leave retained old reservations outside the active policy. The namespace contains no resources other than this ledger.
Durable execution
Kobe uses the same supervisor for wait-mode and detached commands. A raw
Kubernetes exec cannot apply a working directory without a shell, cannot always
recover the exact exit code, and dies with its connection. A pool therefore
offers the durable execution API only if its image ships kobe-runner and the
template says where:
template:
defaultContainer: workspace
runnerPath: /kobe-runner
Copy the binary into your own agent image:
COPY --from=zondax/kobe-runner:latest /kobe-runner /kobe-runner
RUN install -d -o 65532 -g 65532 -m 0700 /var/run/kobe/executions
USER 65532:65532
Replace 65532:65532 with the UID and GID your workload uses. The directory must
exist and be writable by that identity; ordinary non-root users cannot create
new directories directly under /var/run. The runner is a static binary with
no dependencies on the base image, and at runtime it needs no privileges beyond
starting a process and writing the execution spool.
That spool is not an isolation boundary. The runner and workload use the same
container identity, so the workload can remove or replace its files. Kobe never
restores spawn authority from a missing spool and never uses a runner report as
proof that target capacity is safe to reuse. An execution that crossed
startedAt therefore holds one active slot until that exact Sandbox target is
destroyed.
attachCommand
A bare kobe attach joins the container's own process. For an image whose job
is to host sessions that is the wrong landing place: the caller gets the idle
process, and reconnecting starts over instead of resuming. Declare what attach
should run instead:
template:
defaultContainer: agent
attachCommand: ["zellij", "attach", "--create", "kobe"]
Now kobe attach dev lands in a multiplexer session that outlives the
connection, so a dropped laptop costs nothing. The caller's own -- argv still
wins, and a pool that declares nothing keeps the existing behaviour.
The command comes from the pool, never the caller, for the same reason
runnerPath does: it becomes an exec under Kobe's credential. Declaring it
also changes which subresource attach calls, from pods/attach to pods/exec,
so the grant it needs is the exec verb either way.
Without runnerPath, POST /v1/sandbox-leases/{id}/executions returns
501 Not Implemented in both modes. That is deliberate: selecting wait mode
must not silently remove working-directory, exact-exit-code, process-group
cancellation, or reconnectability guarantees.
Output from every durable execution is retained inside the container until the
Sandbox is torn down and can be read back through
GET /v1/sandbox-leases/{id}/executions/{execution}/logs. Kobe retains at most
1 MiB of stdout and 1 MiB of stderr per execution, and accepts at most 256
idempotency keys — durable executions — over one Sandbox lifetime. An execution
that crossed startedAt holds its slot until that exact Sandbox target is
destroyed, because same-UID runner state cannot safely prove capacity is
reusable; the per-lifetime count is therefore the only execution budget, sized
for long-running agent sessions rather than a small per-target batch.
Requests beyond the limit return 409 Conflict with reason
execution_limit_exhausted: retrying cannot succeed, a new lease must be
requested.
Cancelling an execution asks the runner to terminate its whole process group, not only the process Kobe started. Lease release first closes new access, then asks every recorded runner to cancel. Runner state remains tenant-controlled, so Kobe retains each started execution record and slot, destroys the exact Sandbox target, and retires them only after target absence is proven. Execution records are deleted with that Sandbox rather than kept as history after it ends.
Placement and isolation
Use placement.type: management to place upstream Sandbox objects in Kobe's
management cluster. To compose a Sandbox with a child cluster from a
same-namespace ClusterPool, use exactly one child placement instead:
placement:
type: childCluster
clusterPoolRef: ci-sandbox
Isolation tier trusted-runc must omit runtimeClassName. Reserved tiers
gvisor and kata require the exact administrator-selected RuntimeClass name,
but remain ineligible until issue #14 qualifies their complete runtime and node
contract. Lease callers cannot override either placement or isolation.
See the API reference for lease operations and authentication and authorization for Sandbox policy verbs and limits.