Skip to content
Pools

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:

  • disabled is 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.
  • external installs, upgrades, and uninstalls nothing. The operator owns the Agent Sandbox v1.0.0 core, extensions, CRDs, and controller.
  • managed installs the chart-pinned v1.0.0 runtime by controller image digest, retains its CRDs, RBAC, and controller on Helm uninstall, and publishes a retained child BootstrapConfig from 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 ValidatingAdmissionPolicy that permits Lease mutations only from the exact Kobe operator ServiceAccount;
  • a namespace-wide Lease object quota, configured by agentSandbox.reservationObjectLimit (default 4096, minimum 4); 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.

Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi