Skip to main content

Kubernetes Orchestration

STELLA uses Kubernetes to dynamically deploy and manage AI agent pods. Each conversation session gets its own isolated pod with dedicated resources.

Architecture

┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Namespace: ai-agents │ │
│ │ │ │
│ │ ┌─────────────┐ Manages ┌──────────────────────┐ │ │
│ │ │ Backend │──────────▶ │ Agent Pods │ │ │
│ │ │ (NestJS) │ │ ┌────┐ ┌────┐ ┌───┐ │ │ │
│ │ └─────────────┘ │ │Pod1│ │Pod2│ │...│ │ │ │
│ │ │ │ └────┘ └────┘ └───┘ │ │ │
│ │ │ └──────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ ┌──────────────────────┐ │ │
│ │ │ Secrets │ │ ConfigMaps │ │ │
│ │ │ (per pod) │ │ (shared config) │ │ │
│ │ └─────────────┘ └──────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Pod Lifecycle

1. Session Created

When a session is created, the backend prepares the Kubernetes resources:

// Backend creates session
async createSession(dto: CreateSessionDto) {
// 1. Create session record in database
const session = await this.prisma.session.create({
data: {
projectId: dto.projectId,
agentType: dto.agentType,
status: 'PENDING'
}
});

// 2. Create Kubernetes secret with credentials
await this.k8s.createSecret(session.id, {
LIVEKIT_URL: process.env.LIVEKIT_URL,
LIVEKIT_API_KEY: process.env.LIVEKIT_API_KEY,
LIVEKIT_API_SECRET: process.env.LIVEKIT_API_SECRET,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
ROOM_NAME: session.roomName,
SESSION_ID: session.id
});

// 3. Create agent pod
await this.k8s.createAgentPod(session.id, dto.agentType);

return session;
}

2. Pod Specification

Agent pods are created with specific resource requirements:

apiVersion: v1
kind: Pod
metadata:
name: agent-{session-id}
namespace: ai-agents
labels:
app: stella-agent
session: {session-id}
agent-type: stella-agent
spec:
containers:
- name: agent
image: ghcr.io/c4dhi/stella-agent:latest
envFrom:
- secretRef:
name: agent-secret-{session-id}
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "2Gi"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
restartPolicy: Never
terminationGracePeriodSeconds: 30

3. Pod Monitoring

The backend watches pod status changes:

// Watch for pod status changes
k8sWatch.watch(
'/api/v1/namespaces/ai-agents/pods',
{ labelSelector: 'app=stella-agent' },
(type, pod) => {
const sessionId = pod.metadata.labels.session;

switch (type) {
case 'MODIFIED':
if (pod.status.phase === 'Running') {
// Pod is ready, update session status
this.updateSessionStatus(sessionId, 'ACTIVE');
}
break;

case 'DELETED':
// Pod was deleted, clean up session
this.handlePodDeleted(sessionId);
break;
}
}
);

4. Session Termination

When a session ends, resources are cleaned up:

async endSession(sessionId: string) {
// 1. Update session status
await this.prisma.session.update({
where: { id: sessionId },
data: { status: 'ENDING' }
});

// 2. Delete the pod (triggers graceful shutdown)
await this.k8s.deleteNamespacedPod(
`agent-${sessionId}`,
'ai-agents'
);

// 3. Delete the secret
await this.k8s.deleteNamespacedSecret(
`agent-secret-${sessionId}`,
'ai-agents'
);

// 4. Mark session as ended
await this.prisma.session.update({
where: { id: sessionId },
data: {
status: 'ENDED',
endedAt: new Date()
}
});
}

Resource Management

Resource Limits by Agent Type

Agent TypeCPU RequestCPU LimitMemory RequestMemory Limit
stella-agent250m1000m512Mi2Gi
stella-light100m500m256Mi1Gi
echo-agent50m250m128Mi512Mi

Namespace Quotas

Limit total resources in the namespace:

apiVersion: v1
kind: ResourceQuota
metadata:
name: ai-agents-quota
namespace: ai-agents
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"

Limit Ranges

Set default resource limits for pods:

apiVersion: v1
kind: LimitRange
metadata:
name: ai-agents-limits
namespace: ai-agents
spec:
limits:
- default:
cpu: "500m"
memory: 1Gi
defaultRequest:
cpu: "100m"
memory: "256Mi"
type: Container

Pod Security

Secret Management

Secrets are created per-session and contain:

apiVersion: v1
kind: Secret
metadata:
name: agent-secret-{session-id}
namespace: ai-agents
type: Opaque
data:
LIVEKIT_URL: {base64-encoded}
LIVEKIT_API_KEY: {base64-encoded}
LIVEKIT_API_SECRET: {base64-encoded}
OPENAI_API_KEY: {base64-encoded}
ROOM_NAME: {base64-encoded}
SESSION_ID: {base64-encoded}

Security Context

Pods run with restricted permissions:

spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: agent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL

Network Policies

Restrict network access for agent pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-network-policy
namespace: ai-agents
spec:
podSelector:
matchLabels:
app: stella-agent
policyTypes:
- Egress
egress:
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
# Allow LiveKit
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443
- protocol: UDP
port: 443
# Allow OpenAI API
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443

Scaling Considerations

Horizontal Pod Autoscaling

While each session has its own pod, you can use HPA for shared services:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: backend-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: session-management-server
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

Node Affinity

Ensure agent pods run on appropriate nodes:

spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload-type
operator: In
values:
- ai-agents

Monitoring

Pod Metrics

Expose metrics for monitoring:

# In agent code
from prometheus_client import Counter, Histogram

messages_processed = Counter(
'agent_messages_processed_total',
'Total messages processed',
['agent_type', 'session_id']
)

response_latency = Histogram(
'agent_response_latency_seconds',
'Response generation latency',
['agent_type']
)

Health Checks

Implement health endpoints:

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
async def health():
return {"status": "healthy"}

@app.get("/ready")
async def ready():
if agent.is_connected():
return {"status": "ready"}
return Response(status_code=503)

Next Steps