Documentation
¶
Overview ¶
Package sandbox is the tenant-neutral gVisor/runsc sandbox runtime spine shared by apoxy's workerd host and the clrk agent worker.
It is the neutral CORE: the bare pull -> OCI bundle -> runsc -> cgroup-v2 -> sentrystack-loopback lifecycle, with the agent-lineage, identity, egress, trust and persistent-state coupling kept OUT. It lives here in apoxy as the lower, foundational layer so that both consumers depend in one direction (consumer -> core) and the repos never form a dependency cycle:
- apoxy's own workerd host (pkg/workerd/host, cmd/workerd-*) imports it in-tree for a bare single-tenant, inbound-capable sandbox — a workerd sandbox is just an OCI image whose Spec.Command is `workerd serve`.
- clrk's internal/worker/sandbox re-points onto this package across the module boundary (clrk's apoxy pin) and layers the tenant/egress concerns back on as a thin wrapper, so the agent worker keeps its full egress-capable behavior.
One source of truth, not a second copy.
The seam is the Runtime interface — the tenant-neutral lifecycle (Create/Start/Stop/Kill/Wait/Delete/Purge/Status/List/Cleanup) — plus the optional EgressController extension. Above the seam is policy + fan-out (which tenant, which revision, how many resident); below it is mechanism (ORAS pull, OCI bundle, runsc, cgroup-v2, the sentrystack loopback NIC). Nothing below the seam knows about tenants, Kubernetes, or revisions. The tenant wrapper in clrk's internal/worker/sandbox plugs egress, identity, trust and persistent state back in through the core's extension seams (Spec.Mounts, the EgressController setters, the sentrystack init payload).
Index ¶
- Variables
- func DispatchRunsc()
- func InboundSockPath(stateDir string, id SandboxID) string
- func InitHostCgroup() (string, error)
- func ShouldSkipReap(pid int) bool
- type BackendListener
- type EgressController
- type EgressInit
- type ImageInfo
- type ImageStore
- type ImageStoreOption
- type Instance
- type Manager
- func (m *Manager) Cleanup(ctx context.Context) error
- func (m *Manager) Create(ctx context.Context, spec Spec) (*Instance, error)
- func (m *Manager) Delete(ctx context.Context, id SandboxID) error
- func (m *Manager) EnsureImage(ctx context.Context, ref string, cred auth.Credential) (*ImageInfo, error)
- func (m *Manager) ImageStore() *ImageStore
- func (m *Manager) Kill(ctx context.Context, id SandboxID) error
- func (m *Manager) List() []*Instance
- func (m *Manager) Purge(ctx context.Context, id SandboxID)
- func (m *Manager) Start(ctx context.Context, id SandboxID) error
- func (m *Manager) Status(ctx context.Context, id SandboxID) (*Instance, error)
- func (m *Manager) Stop(ctx context.Context, id SandboxID) error
- func (m *Manager) Wait(ctx context.Context, id SandboxID) (int, error)
- type ManagerConfig
- type Mount
- type Policy
- type Runtime
- type SandboxID
- type SandboxPhase
- type Spec
- type StdioSink
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAlreadyExists is returned by Create when a sandbox with the given ID // is already live. ErrAlreadyExists = errors.New("sandbox already exists") // ErrNotFound is returned when no sandbox with the given ID exists. ErrNotFound = errors.New("sandbox not found") )
Functions ¶
func DispatchRunsc ¶
func DispatchRunsc()
DispatchRunsc hands off to gVisor's runsc entrypoint when the process was re-exec'd as a runsc subcommand. A host binary that drives this package's Manager must call DispatchRunsc() as the very first thing in main(), before any other initialization: the same /proc/self/exe is re-exec'd for every runsc create/start/kill/wait/delete and for the Sentry boot/gofer children, and those invocations must reach gVisor's maincli rather than the host's normal control loop.
The runsc-exec helpers in this package put runsc-level flags (e.g. --root, --network) BEFORE the subcommand, mirroring the runsc CLI; so scan past leading flag-shaped argv tokens before checking for a known subcommand. maincli.Main never returns (cli.Run terminates via os.Exit), so this function only returns when argv contained no recognized subcommand at all — i.e. this is the host's primary invocation, not a re-exec.
func InboundSockPath ¶
InboundSockPath returns the host filesystem path of the AF_UNIX listening socket that fronts a sandbox's resident server. Lives under the runsc state dir alongside the rest of the per-sandbox state. Kept short to stay under the 108-byte sun_path limit — callers composing ids into this path (see pkg/workerd/names) guard the budget with tests.
Exported (and kept build-tag free) because the path is a cross-component contract: the manager creates the socket here and the gateway's xDS translator emits the same path into the resident Envoy cluster on any platform.
func InitHostCgroup ¶
InitHostCgroup performs one-time cgroup v2 setup so the host can enforce per-sandbox CPU/memory limits via per-sandbox children:
- parses /proc/self/cgroup; fails closed if the host isn't cgroup v2 (legacy v1/hybrid hosts would silently lose enforcement);
- mkdir <host>/init and moves the host PID into it so <host> itself becomes empty;
- writes "+memory +cpu" to <host>/cgroup.subtree_control to delegate enforcement to per-sandbox children;
- mkdir <host>/system as the per-sandbox parent.
Returns the absolute filesystem path of the host cgroup (e.g. "/sys/fs/cgroup/kubepods.slice/.../cri-containerd-xxx.scope").
func ShouldSkipReap ¶
ShouldSkipReap reports whether the reaper should skip the given PID because cmd.Wait() will collect it. Exported so the host binary's SIGCHLD reaper can import it.
Types ¶
type BackendListener ¶
type BackendListener struct {
// Name mirrors the EgressListener name from the CRD; used for logs and for
// EgressL4Route parentRef.sectionName matching.
Name string
// Addr is the host:port the sandbox dials. Empty means "listener exists
// but its data plane isn't ready yet" — the dialer skips it.
Addr string
// Shape is the on-the-wire protocol selector ("tls-terminate",
// "tls-passthrough", "tcp", "http", "https"). Diagnostic surface; the
// dialer tie-breaks on Priority, not Shape.
Shape string
// MatchPort, when non-zero, narrows this listener to the sandbox's
// destination port. Zero = catch-all for this shape.
MatchPort int32
// Priority is the precomputed shape rank used as a tiebreaker when
// multiple listeners match the same dst port. Higher wins.
Priority int
}
BackendListener describes one EgressGateway listener the sandbox can dial. It is the pure (CRD-free) egress.BackendListener shape from clrk; the egress track consumes it unchanged.
type EgressController ¶
type EgressController interface {
// SetEgressBackends installs the set of EgressGateway listeners the
// sandbox may dial. Live-swappable: replaces the prior set.
SetEgressBackends(id SandboxID, backends []BackendListener) error
// SetEgressPolicy installs the per-sandbox egress authorization plane.
// Live-swappable; a nil policy means allow-all (no MITM, no enforcement).
SetEgressPolicy(id SandboxID, policy *Policy) error
// SetInvocationID stamps the current invocation ID carried in PROXY v2
// TLVs on egress connections dialed through this sandbox, for attribution.
SetInvocationID(id SandboxID, invocationID string) error
}
EgressController is the OPTIONAL egress-policy extension of a Runtime. The core Runtime is deliberately tenant- and egress-neutral; an implementation that also mediates egress (clrk's internal worker wrapper does, via the worker egress bridge) additionally satisfies EgressController so the egress config plane — APO-723's localhost Config/ApplyEgress gRPC — can push live routing/policy/attribution without the core seam growing egress concerns.
This package defines the signatures only; the implementation lives in clrk's internal/worker/sandbox wrapper, which retains the egress data path (egress_bridge, the sentrystack forwarders, PROXY v2 identity). The egress track (APO-722/723/726) is what wires an external consumer to it. Until then Policy is a forward declaration the egress track fleshes out.
A caller probes for egress support with a type assertion:
if ec, ok := rt.(EgressController); ok {
_ = ec.SetEgressBackends(id, backends)
}
type EgressInit ¶
type EgressInit struct {
// IMDSHostAddr is the host-bound IMDS dial target (e.g.
// "127.0.0.1:<port>") the in-Sentry forwarder dials for IMDS traffic.
IMDSHostAddr string
// EgressHostAddr is the host-bound egress dial target for every
// non-IMDS, non-DNS outbound stream.
EgressHostAddr string
// IMDSV4 / IMDSV6 are the link-local IMDS addrs ("ip:port") the
// forwarder matches outbound dst against to route IMDS-vs-direct.
IMDSV4 string
IMDSV6 string
// DNSResolvers is the host-side resolver list ("ip:port") the
// forwarder dials for outbound :53.
DNSResolvers []string
}
EgressInit is the opaque set of sentrystack egress-routing fields a Spec carries for an egress-capable embedder (clrk's wrapper). The core never acts on them — it copies them into the per-sandbox init payload the in-Sentry forwarder reads. Mirrors the egress fields of sentrystack.InitStr; kept as a flat neutral struct so the core Spec surface doesn't grow IMDS/MITM vocabulary of its own.
type ImageInfo ¶
type ImageInfo struct {
RootFS string // Path to extracted rootfs directory.
Entrypoint []string // Default entrypoint from image config.
Cmd []string // Default cmd from image config.
}
ImageInfo holds metadata for a pulled image.
type ImageStore ¶
type ImageStore struct {
// contains filtered or unexported fields
}
ImageStore manages OCI image pulling and rootfs extraction. Images are pulled via ORAS and their layers extracted to a shared rootfs directory.
func NewImageStore ¶
func NewImageStore(baseDir string, opts ...ImageStoreOption) *ImageStore
NewImageStore creates a new ImageStore rooted at baseDir.
func (*ImageStore) CachedRefs ¶
func (s *ImageStore) CachedRefs() []string
CachedRefs returns the image references currently resident in the store. A caller can use this to prefer a host that already has a revision's image when placing work (image affinity).
func (*ImageStore) EnsureImage ¶
func (s *ImageStore) EnsureImage(ctx context.Context, imageRef string, cred auth.Credential) (*ImageInfo, error)
EnsureImage pulls and extracts the OCI image if not cached. Returns image info including the path to the extracted rootfs directory. Concurrent calls for the same imageRef are deduplicated via singleflight. cred authenticates the pull against a private registry; the zero value pulls anonymously. It does not key the cache — a ref names the same content regardless of who pulls it.
type ImageStoreOption ¶
type ImageStoreOption func(*ImageStore)
ImageStoreOption configures an ImageStore.
func WithImageStoreLogger ¶
func WithImageStoreLogger(l *slog.Logger) ImageStoreOption
WithImageStoreLogger sets the structured logger the store writes pull/extract progress to. Defaults to slog.Default().
type Instance ¶
type Instance struct {
// ID is the sandbox identifier from [Spec.ID].
ID SandboxID
// Phase is the current lifecycle phase.
Phase SandboxPhase
// RootFS is the extracted rootfs path on the host.
RootFS string
// SandboxIP is the per-sandbox container IP written into the in-Sentry
// PluginStack eth0 via the sentrystack init payload.
SandboxIP netip.Addr
// InboundSocket is the host filesystem path of the AF_UNIX listening
// socket that fronts the in-sandbox resident server, set at Start when
// [Spec.InboundListenAddr] was requested. Callers on the host (an Envoy
// upstream cluster, the backplane bridge, or an acceptance test) dial
// this path to reach the in-sandbox listener — there is no host route to
// the in-Sentry SandboxIP, so this socket is the ingress entry point.
// Empty when the sandbox is egress-only.
InboundSocket string
// GatewayIP is the per-sandbox default-route gateway. Cosmetic under
// sentrystack (the in-Sentry forwarder never delivers frames to it) but
// exposed so `ip route` inside the sandbox shows a sane default route and
// so /etc/resolv.conf has a destination that triggers the in-Sentry DNS
// forwarder.
GatewayIP netip.Addr
// Stdin/Stdout/Stderr are populated only when the sandbox was Created with
// [Spec.Stdio] true; nil otherwise.
Stdin io.WriteCloser
Stdout io.ReadCloser
Stderr io.ReadCloser
// CreatedAt is when Create completed.
CreatedAt time.Time
// contains filtered or unexported fields
}
Instance is the runtime's view of a single live sandbox. It is the neutral core of clrk's sandbox.Instance: the CRD AgentSandbox / ExecutionResources snapshots, the proxyproto.AgentIdentity, and the agent-lineage AgentRef/Namespace fields are kept on clrk's wrapper, not here (§3.6).
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager is the tenant-neutral gVisor/runsc sandbox lifecycle runtime — the production implementation of Runtime. It pulls OCI images, builds per-sandbox OCI bundles, and drives `runsc create/start/kill/wait/ delete` subprocesses that re-enter the same binary via DispatchRunsc.
It carries no agent-lineage, identity, trust, egress, or Kubernetes coupling: those live on clrk's internal/worker/sandbox wrapper, which embeds this Manager, adapts its CRD CreateRequest down to a Spec, and re-adds the egress data path + OTLP stdio + persistent-state mounts. An external consumer (workerd-host) embeds this Manager directly.
func NewManager ¶
func NewManager(cfg ManagerConfig) *Manager
NewManager constructs the sandbox lifecycle manager.
func (*Manager) Cleanup ¶
Cleanup reconciles on-host runsc state against the in-process instance table, reaping orphans left by a previous host incarnation. Scans the runsc --root dir directly rather than forking `runsc list`, covering both the per-container-directory layout and the flat "<id>_sandbox:<sid>.state" metadata files newer runsc writes into the root — missing the latter left every post-restart `runsc create` failing with "container already exists".
func (*Manager) Create ¶
Create pulls the image, builds an OCI bundle, and runs `runsc create` to spawn the Sentry. The sandbox is left in the Ready phase for resident-pool use — Start is a separate call.
func (*Manager) Delete ¶
Delete destroys the sandbox and frees its on-host state. `runsc delete --force` SIGKILLs anything still running.
func (*Manager) EnsureImage ¶
func (m *Manager) EnsureImage(ctx context.Context, ref string, cred auth.Credential) (*ImageInfo, error)
EnsureImage pulls (or returns cached metadata for) ref via the manager's underlying ImageStore. Exposed so an embedder can resolve the extracted rootfs (e.g. to compute rootfs-dependent mounts) before building the Spec it hands to Create — the pull is singleflight-cached so the re-pull inside Create is free. cred authenticates against a private registry; the zero value pulls anonymously.
func (*Manager) ImageStore ¶
func (m *Manager) ImageStore() *ImageStore
ImageStore returns the manager's image store so callers that need the cached-refs query can reach it without crossing into private state.
func (*Manager) Purge ¶
Purge is the best-effort, error-swallowing teardown used on cleanup paths where a hung Delete must not pin the caller. Safe to call before Create against a stale ID: runsc delete is idempotent for not-found containers, and the in-memory registration is released so the id is immediately re-creatable (crash self-heal, per-tenant stop-then-recreate).
func (*Manager) Start ¶
Start fires the Sentry's spec.Process. The user binary is running inside the sandbox after this returns.
func (*Manager) Status ¶
Status returns the current Instance for id, or ErrNotFound. It refreshes Phase from `runsc state`.
type ManagerConfig ¶
type ManagerConfig struct {
// StateDir is runsc's --root directory; one subdirectory per sandbox
// keyed by container ID.
StateDir string
// RootDir holds per-sandbox netconfig (resolv.conf) and is the
// conventional home for caller-supplied bind-mount sources.
RootDir string
// ImageStore pulls and extracts OCI images. Shared across all
// sandboxes on this host.
ImageStore *ImageStore
// HostCgroupPath is the absolute path of the host's cgroup v2
// directory as returned by [InitHostCgroup]. Required: Create fails
// if it's empty because per-sandbox enforcement depends on being able
// to mkdir under <HostCgroupPath>/system/<id>.
HostCgroupPath string
// LogSinkFor, when set, is called by Start to obtain the per-sandbox
// stdio log sink (see [StdioSink]). Optional; nil discards the stdio
// log copy.
LogSinkFor func(id SandboxID) StdioSink
}
ManagerConfig bundles the construction-time inputs of NewManager.
type Mount ¶
type Mount struct {
// Source is the host path (for a bind) or fs source ("tmpfs", "proc", ...).
Source string
// Destination is the in-sandbox mount point.
Destination string
// Type is the mount type ("bind", "tmpfs", ...). Empty defaults to "bind".
Type string
// Options are mount options (e.g. "ro", "nosuid", "rbind").
Options []string
}
Mount is one extra mount layered into the sandbox rootfs. It maps directly to an OCI runtime-spec mount in the manager's buildSpec.
type Policy ¶
type Policy struct{}
Policy is the per-sandbox egress authorization plane an EgressController installs. It is a forward-declared seam type in the spine: its concrete shape (route table + default decision) is CRD-coupled in clrk (egress.SandboxPolicy) and is populated by the egress track (APO-722/723) when the egress data path is wired to an external consumer. The core Runtime never reads it.
type Runtime ¶
type Runtime interface {
// Create pulls Spec.Image (ORAS), extracts the rootfs, builds the OCI
// bundle and runs `runsc create`. The returned Instance is in phase Ready
// (created, not started). Returns [ErrAlreadyExists] if Spec.ID is live.
Create(ctx context.Context, spec Spec) (*Instance, error)
// Start runs `runsc start`: the Sentry boots, the sentrystack PluginStack
// is initialized (loopback NIC + inbound forwarder), and the guest process
// is forked. Moves the instance Ready -> Running.
Start(ctx context.Context, id SandboxID) error
// Stop sends SIGTERM and moves the instance Running -> Stopping. It does
// NOT block for exit: the caller polls Status / Wait for the eventual
// transition to Stopped (the graceful-drain loop lives above the seam, as
// it does in clrk). The instance lands in Stopped synchronously only if it
// had already exited before the signal.
Stop(ctx context.Context, id SandboxID) error
// Kill sends SIGKILL — the non-graceful stop.
Kill(ctx context.Context, id SandboxID) error
// Wait blocks until the guest process exits and returns its exit code.
Wait(ctx context.Context, id SandboxID) (exitCode int, err error)
// Delete tears down the runsc container and frees its on-host state
// (rootfs, cgroup, bundle). Returns [ErrNotFound] if the id is unknown.
Delete(ctx context.Context, id SandboxID) error
// Purge is the best-effort, error-swallowing teardown used on cleanup
// paths where a hung Delete must not pin the caller. It never returns an
// error by design.
Purge(ctx context.Context, id SandboxID)
// Status returns the current Instance for id, or [ErrNotFound].
Status(ctx context.Context, id SandboxID) (*Instance, error)
// List returns a snapshot of all live instances.
List() []*Instance
// Cleanup reconciles on-host runsc state against the in-process instance
// table, reaping orphans left by a previous host incarnation.
Cleanup(ctx context.Context) error
}
Runtime is the tenant-neutral gVisor/runsc sandbox lifecycle seam — the spine's load-bearing interface. The production implementation is *Manager; unit tests and the downstream stubs that land first against this interface — ServiceManager (APO-796), workerd-host (APO-625), the egress config sink (APO-723) — implement it with a fake until the real manager arrives.
The methods correspond 1:1 to clrk's worker sandbox manager. The interface exists so callers off-platform (darwin) and in tests can name the seam without the linux-only runsc plumbing, exactly as clrk's agents.SandboxRuntime does — but here the three egress setters are split out into the optional EgressController extension so the core stays tenant- and egress-neutral and clrk's internal wrapper owns the egress data path.
type SandboxID ¶
type SandboxID string
SandboxID uniquely identifies a sandbox instance within a host.
type SandboxPhase ¶
type SandboxPhase string
SandboxPhase is the lifecycle phase of a sandbox.
const ( // SandboxCreating is set while Create is pulling/extracting/bundling. SandboxCreating SandboxPhase = "Creating" // SandboxReady is a created-but-not-started sandbox (the resident-pool // warm state): runsc create has run, the guest has not been forked. SandboxReady SandboxPhase = "Ready" // SandboxRunning is an active sandbox: runsc start has forked the guest. SandboxRunning SandboxPhase = "Running" // SandboxStopping is a sandbox that has been signalled and is awaiting exit. SandboxStopping SandboxPhase = "Stopping" // SandboxStopped is an exited sandbox (not yet Deleted). SandboxStopped SandboxPhase = "Stopped" )
type Spec ¶
type Spec struct {
// ID is the caller-assigned sandbox identifier. Must be unique among live
// sandboxes on the host; Create returns [ErrAlreadyExists] otherwise.
ID SandboxID
// Image is the OCI image reference the runtime pulls (ORAS) and extracts
// into the sandbox rootfs. For workerd this is the bundle image whose
// command is `workerd serve`.
Image string
// ImagePullCredential authenticates the Image pull against a private
// registry. The zero value pulls anonymously.
ImagePullCredential auth.Credential
// Command overrides the image entrypoint (argv from index 0). Empty uses
// the image's own entrypoint. (Adapted from clrkv1alpha1.AgentSandbox.)
Command []string
// Args are appended after Command.
Args []string
// Env is the process environment as "KEY=VALUE" strings. Adapted from
// clrk's []corev1.EnvVar — the core carries no Kubernetes core types.
Env []string
// CPUMillis is the cgroup-v2 cpu.max budget in milli-CPU (1000 == one
// core). Zero leaves cpu.max at "max". Adapted from
// ExecutionResources.CPU.MilliValue().
CPUMillis int64
// MemBytes is the cgroup-v2 memory.max budget in bytes. Zero leaves
// memory.max at "max". Adapted from ExecutionResources.Memory.Value().
MemBytes int64
// Mounts are extra mounts layered onto the default OCI mount set inside
// the jail (e.g. a bundle config dir, a UDS directory). This is the
// extension seam clrk's wrapper uses to re-add trust and persistent-state
// bind mounts without the core knowing about them.
Mounts []Mount
// Stdio requests caller-facing stdio pipes on the resulting Instance
// (Instance.Stdin/Stdout/Stderr). A resident socket-server workerd does
// not need them; one-shot stdio callers do.
Stdio bool
// Annotations are OCI annotations stamped onto the container config.
// clrk's wrapper computes agent-lineage labels here; the core treats
// them as opaque key/value metadata (recoverable out-of-band via
// `runsc state`). Nil is fine.
Annotations map[string]string
// Egress carries the opaque sentrystack egress-routing fields an
// egress-capable embedder wants merged into the per-sandbox init
// payload. The core stamps only the addressing fields it computes
// itself (eth0 IP/MAC, gateway) and copies these through verbatim;
// it never acts on them. Zero value = a sandbox with lo + eth0 and
// no egress routing (direct dial), which is the standalone-core path.
Egress EgressInit
// InboundListenAddr opts the sandbox into the ingress path: the
// in-sandbox "ip:port" a resident server listens on (e.g.
// "127.0.0.1:8080"). When set, Create seals it into the sentrystack
// init payload (InboundListenAddr + InboundFDIndex) before the Sentry
// boots, and Start opens a host AF_UNIX listening socket fronting the
// resident server, donating its fd so the in-Sentry inbound forwarder
// can accept on it. The host socket path is surfaced on
// [Instance.InboundSocket]. Unlike [Spec.Egress] this is tenant-neutral
// and acted on by the core directly, so a standalone consumer
// (workerd-host) gets ingress with no egress installer. Empty keeps the
// sandbox egress-only.
//
// It must be set here, not on the returned Instance: Create seals the
// initStr before returning, so a value assigned post-Create never
// reaches the Sentry's PreInit and the forwarder silently never installs.
InboundListenAddr string
// ControlForwardAddr opts the sandbox into the control path: the in-sandbox
// "ip:port" a resident dispatcher dials to reach the host manager's control
// server (e.g. "127.0.0.2:80", distinct from InboundListenAddr's 127.0.0.1
// data socket). When set together with ControlHostAddr, Create seals both
// into the sentrystack init payload and the in-Sentry control forwarder
// accepts the dispatcher's connections on an in-stack listener and splices
// each to the host control listener. Like InboundListenAddr this is
// tenant-neutral and acted on by the core; unlike inbound it needs no fd
// donation — the Sentry shares the host net namespace and dials the manager's
// loopback control listener directly. Empty leaves the sandbox without a
// control plane.
//
// Like InboundListenAddr it must be set here, not on the returned Instance:
// Create seals the initStr before returning.
ControlForwardAddr string
// ControlHostAddr is the HOST loopback "ip:port" of the manager's control
// server (TCP) the in-Sentry control forwarder dials for each guest
// connection to ControlForwardAddr. TCP, not AF_UNIX: the Sentry's plugin
// seccomp filter only allows socket() for AF_INET/AF_INET6, so a host unix
// dial from inside the Sentry returns ENOSYS. Empty (regardless of
// ControlForwardAddr) disables control forwarding.
ControlHostAddr string
}
Spec is the per-call input to Runtime.Create. It is the tenant-neutral core of clrk's sandbox.CreateRequest: the CRD-shaped AgentSandbox / ExecutionResources, the []corev1.EnvVar, the proxyproto.AgentIdentity, the CAPEM trust bundle and the persistent AgentState are all kept out of the core (the four de-contamination seams of §3.6) so the spine carries no agent-lineage, identity, trust, or Kubernetes coupling. clrk's internal wrapper adapts its CRD CreateRequest down to this Spec; an external caller describes a sandbox purely as "this OCI image, run this argv with this env under these cgroup limits, with these extra mounts".
type StdioSink ¶
type StdioSink struct {
// Stdout/Stderr receive the sandbox's stdout/stderr byte stream. nil
// is treated as io.Discard.
Stdout io.Writer
Stderr io.Writer
// Close flushes + releases the sink. Called exactly once per sink,
// after the sandbox exits or is deleted. nil is a no-op.
Close func()
}
StdioSink is the per-sandbox log destination an embedder attaches to a sandbox's stdout/stderr at Start. The core drains the Sentry's stdio into Stdout/Stderr (in addition to the caller-facing pipes in stdio mode) and calls Close once the sandbox has exited (Wait) or is torn down (Delete) so the embedder can flush + release its writers.
clrk's wrapper returns a sink that fans each line into the worker's OTLP logs logger + an identity-tagged slog + a per-agent log file; a standalone core consumer can leave Manager's sink hook unset, in which case the log copy is discarded and only the caller-facing pipes receive the stdio.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
The InitStr envelope is pure JSON with no gvisor dependencies — leave it without a //go:build constraint so cross-platform unit tests can exercise Encode/Decode without pulling in linux-only gvisor packages.
|
The InitStr envelope is pure JSON with no gvisor dependencies — leave it without a //go:build constraint so cross-platform unit tests can exercise Encode/Decode without pulling in linux-only gvisor packages. |