Skip to content

Commit f6b1f87

Browse files
stubbiclaude
andauthored
feat: managed inference, persistence, multi-namespace CRD support (#11)
* feat: add managed inference support to CRD and env var injection Add ManagedInferenceSecretRef, ManagedInferenceProvider, and ManagedInferenceModel fields to AdaptersSpec. The operator injects PAPERCLIP_MANAGED_INFERENCE_API_KEY from the referenced Secret, plus plain env vars for provider and model when set. Includes unit tests for both configured and unconfigured cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: Phase 4 - persistence, multi-namespace, inference proxy CRD support Add production hardening fields to the CloudSandboxSpec CRD: - CloudSandboxPersistenceSpec for PVC-backed persistent workspaces - MultiNamespace flag for per-company namespace isolation - InferenceProxySpec for transparent inference metering proxy sidecar - ResourceTiers map for named resource presets Operator changes: - Inject persistence and multi-namespace env vars into the StatefulSet - Add PVC permissions to sandbox Role when persistence is enabled - Add ClusterRole/ClusterRoleBinding builders for multi-namespace mode with namespace create/get/list permissions - Extract buildCloudSandboxEnvVars to keep cyclomatic complexity in check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f4c44cb commit f6b1f87

7 files changed

Lines changed: 797 additions & 36 deletions

File tree

api/v1alpha1/paperclipinstance_types.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,21 @@ type AdaptersSpec struct {
356356
// CloudSandbox configures cloud-based agent execution in isolated Kubernetes pods.
357357
// +optional
358358
CloudSandbox *CloudSandboxSpec `json:"cloudSandbox,omitempty"`
359+
360+
// ManagedInferenceSecretRef references a Secret containing the platform LLM API key.
361+
// The Secret must contain a key "PAPERCLIP_MANAGED_INFERENCE_API_KEY".
362+
// +optional
363+
ManagedInferenceSecretRef *corev1.LocalObjectReference `json:"managedInferenceSecretRef,omitempty"`
364+
365+
// ManagedInferenceProvider is the LLM provider for managed inference (e.g. "anthropic", "openrouter").
366+
// +kubebuilder:default="anthropic"
367+
// +optional
368+
ManagedInferenceProvider string `json:"managedInferenceProvider,omitempty"`
369+
370+
// ManagedInferenceModel is the default model for managed inference.
371+
// +kubebuilder:default="claude-sonnet-4-6"
372+
// +optional
373+
ManagedInferenceModel string `json:"managedInferenceModel,omitempty"`
359374
}
360375

361376
// CloudSandboxSpec configures cloud sandbox execution for agent runtimes.
@@ -382,6 +397,49 @@ type CloudSandboxSpec struct {
382397
// Resources specifies default compute resources for sandbox pods.
383398
// +optional
384399
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
400+
401+
// Persistence configures PVC-backed persistent workspaces for sandbox pods.
402+
// +optional
403+
Persistence *CloudSandboxPersistenceSpec `json:"persistence,omitempty"`
404+
405+
// MultiNamespace enables per-company namespace isolation for sandbox pods.
406+
// When enabled, each company's sandbox pods run in a dedicated namespace.
407+
// +optional
408+
MultiNamespace bool `json:"multiNamespace,omitempty"`
409+
410+
// InferenceProxy configures the transparent inference metering proxy.
411+
// +optional
412+
InferenceProxy *InferenceProxySpec `json:"inferenceProxy,omitempty"`
413+
414+
// ResourceTiers defines named resource presets for sandbox pods.
415+
// +optional
416+
ResourceTiers map[string]corev1.ResourceRequirements `json:"resourceTiers,omitempty"`
417+
}
418+
419+
// CloudSandboxPersistenceSpec configures PVC-backed persistent workspaces.
420+
type CloudSandboxPersistenceSpec struct {
421+
// Enabled enables PVC-backed workspaces instead of emptyDir.
422+
Enabled bool `json:"enabled,omitempty"`
423+
// StorageClass is the storage class for workspace PVCs.
424+
// +optional
425+
StorageClass string `json:"storageClass,omitempty"`
426+
// Size is the storage size for workspace PVCs (e.g. "10Gi").
427+
// +kubebuilder:default="10Gi"
428+
// +optional
429+
Size string `json:"size,omitempty"`
430+
}
431+
432+
// InferenceProxySpec configures the transparent inference metering proxy.
433+
type InferenceProxySpec struct {
434+
// Enabled enables the inference proxy sidecar for metered API access.
435+
Enabled bool `json:"enabled,omitempty"`
436+
// Image is the inference proxy container image.
437+
// +optional
438+
Image string `json:"image,omitempty"`
439+
// Port is the port the proxy listens on.
440+
// +kubebuilder:default=8090
441+
// +optional
442+
Port int32 `json:"port,omitempty"`
385443
}
386444

387445
// ConnectionsSpec configures third-party OAuth provider credentials.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/paperclip-operator/templates/crds/paperclip.inc_instances.yaml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,115 @@ spec:
9696
pod can be idle before being reaped.
9797
format: int32
9898
type: integer
99+
inferenceProxy:
100+
description: InferenceProxy configures the transparent inference
101+
metering proxy.
102+
properties:
103+
enabled:
104+
description: Enabled enables the inference proxy sidecar
105+
for metered API access.
106+
type: boolean
107+
image:
108+
description: Image is the inference proxy container image.
109+
type: string
110+
port:
111+
default: 8090
112+
description: Port is the port the proxy listens on.
113+
format: int32
114+
type: integer
115+
type: object
116+
multiNamespace:
117+
description: |-
118+
MultiNamespace enables per-company namespace isolation for sandbox pods.
119+
When enabled, each company's sandbox pods run in a dedicated namespace.
120+
type: boolean
99121
namespace:
100122
description: Namespace is the namespace for sandbox pods.
101123
Defaults to the instance namespace.
102124
type: string
125+
persistence:
126+
description: Persistence configures PVC-backed persistent
127+
workspaces for sandbox pods.
128+
properties:
129+
enabled:
130+
description: Enabled enables PVC-backed workspaces instead
131+
of emptyDir.
132+
type: boolean
133+
size:
134+
default: 10Gi
135+
description: Size is the storage size for workspace PVCs
136+
(e.g. "10Gi").
137+
type: string
138+
storageClass:
139+
description: StorageClass is the storage class for workspace
140+
PVCs.
141+
type: string
142+
type: object
143+
resourceTiers:
144+
additionalProperties:
145+
description: ResourceRequirements describes the compute
146+
resource requirements.
147+
properties:
148+
claims:
149+
description: |-
150+
Claims lists the names of resources, defined in spec.resourceClaims,
151+
that are used by this container.
152+
153+
This is an alpha field and requires enabling the
154+
DynamicResourceAllocation feature gate.
155+
156+
This field is immutable. It can only be set for containers.
157+
items:
158+
description: ResourceClaim references one entry in
159+
PodSpec.ResourceClaims.
160+
properties:
161+
name:
162+
description: |-
163+
Name must match the name of one entry in pod.spec.resourceClaims of
164+
the Pod where this field is used. It makes that resource available
165+
inside a container.
166+
type: string
167+
request:
168+
description: |-
169+
Request is the name chosen for a request in the referenced claim.
170+
If empty, everything from the claim is made available, otherwise
171+
only the result of this request.
172+
type: string
173+
required:
174+
- name
175+
type: object
176+
type: array
177+
x-kubernetes-list-map-keys:
178+
- name
179+
x-kubernetes-list-type: map
180+
limits:
181+
additionalProperties:
182+
anyOf:
183+
- type: integer
184+
- type: string
185+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
186+
x-kubernetes-int-or-string: true
187+
description: |-
188+
Limits describes the maximum amount of compute resources allowed.
189+
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
190+
type: object
191+
requests:
192+
additionalProperties:
193+
anyOf:
194+
- type: integer
195+
- type: string
196+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
197+
x-kubernetes-int-or-string: true
198+
description: |-
199+
Requests describes the minimum amount of compute resources required.
200+
If Requests is omitted for a container, it defaults to Limits if that is explicitly specified,
201+
otherwise to an implementation-defined value. Requests cannot exceed Limits.
202+
More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
203+
type: object
204+
type: object
205+
description: ResourceTiers defines named resource presets
206+
for sandbox pods.
207+
type: object
103208
resources:
104209
description: Resources specifies default compute resources
105210
for sandbox pods.
@@ -161,6 +266,32 @@ spec:
161266
type: object
162267
type: object
163268
type: object
269+
managedInferenceModel:
270+
default: claude-sonnet-4-6
271+
description: ManagedInferenceModel is the default model for managed
272+
inference.
273+
type: string
274+
managedInferenceProvider:
275+
default: anthropic
276+
description: ManagedInferenceProvider is the LLM provider for
277+
managed inference (e.g. "anthropic", "openrouter").
278+
type: string
279+
managedInferenceSecretRef:
280+
description: |-
281+
ManagedInferenceSecretRef references a Secret containing the platform LLM API key.
282+
The Secret must contain a key "PAPERCLIP_MANAGED_INFERENCE_API_KEY".
283+
properties:
284+
name:
285+
default: ""
286+
description: |-
287+
Name of the referent.
288+
This field is effectively required, but due to backwards compatibility is
289+
allowed to be empty. Instances of this type with an empty value here are
290+
almost certainly wrong.
291+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
292+
type: string
293+
type: object
294+
x-kubernetes-map-type: atomic
164295
type: object
165296
auth:
166297
description: Auth configures authentication settings.

0 commit comments

Comments
 (0)