Skip to content

Setting limits

Limits live at two levels: per run (on the Station) and a namespace ceiling (standard Kubernetes objects). Use both: the Station shapes each run, the namespace caps the blast radius.

Set them on the Station. The controller wires the container named agent (command, env, mounts) but preserves whatever else you set on it, including resources.

apiVersion: agents.re-cinq.com/v1alpha1
kind: Station
metadata:
name: node-fixer
namespace: ai-agents
spec:
agentDefRef: bug-fixer
deadlineMinutes: 15 # per-run wall-clock -> Job activeDeadlineSeconds
maxConcurrentRuns: 3 # at most 3 runs of THIS Station at once (0 = unlimited)
concurrencyPolicy: Allow # at the limit: Allow (queue) | Forbid (cap at one) | Replace (cancel oldest)
successfulRunsHistoryLimit: 3 # finished Agents kept before pruning (retention, not concurrency)
failedRunsHistoryLimit: 3
template:
spec:
restartPolicy: Never
containers:
- name: agent
image: node:22-bookworm
resources: # per-run CPU/memory, set these yourself
requests:
cpu: "500m"
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
FieldEffect
resources (on the agent container)CPU/memory requests + limits for the run. The controller does not default these: only the init container is defaulted, so set them here or use a LimitRange (below).
deadlineMinutesWall-clock limit per run; becomes the Job’s activeDeadlineSeconds.
maxConcurrentRunsHow many Agents of this Station may be Running at once. 0 (default) is unlimited. A new Agent created while the Station is at the limit stays Pending and starts automatically once a run finishes.
concurrencyPolicyWhat happens to a new run while at the limit: Allow (default) queues it, Forbid caps the Station at a single run, Replace cancels the oldest Running run and starts the new one (“always run the latest”).
successfulRunsHistoryLimit / failedRunsHistoryLimitHow many finished Agents are kept per phase before the oldest are pruned. Retention only, unrelated to concurrency.

The controller counts the Station’s Agents currently in Running. While that count is at the effective limit, what a new Pending Agent does depends on concurrencyPolicy:

  • Allow (default) — the Agent waits (it is not failed or dropped) and is admitted on the next reconcile once a run completes, up to maxConcurrentRuns at a time. So you can queue work freely. maxConcurrentRuns: 1 gives strict serial execution.
  • Forbid — the Station is capped at a single run regardless of maxConcurrentRuns; extra Agents wait, same as Allow with the limit at one.
  • Replace — the controller cancels the oldest Running run (deleting its Agent, which cascades to its Job) and starts the new one. Use this for “always run the latest”.

Namespace ceiling: bound the whole namespace

Section titled “Namespace ceiling: bound the whole namespace”

maxConcurrentRuns caps one Station; a ResourceQuota caps everything in the namespace, so a burst across many Stations can’t exhaust the cluster.

apiVersion: v1
kind: ResourceQuota
metadata: { name: agent-runs, namespace: ai-agents }
spec:
hard:
requests.cpu: "10" # 10 CPU / 500m-per-run ~= 20 concurrent runs
requests.memory: 20Gi
limits.cpu: "40"
limits.memory: 80Gi

A LimitRange gives default container limits, so a Station that forgets resources isn’t scheduled as BestEffort (the first thing the kernel OOM-kills):

apiVersion: v1
kind: LimitRange
metadata: { name: agent-defaults, namespace: ai-agents }
spec:
limits:
- type: Container
default: { cpu: "1", memory: 1Gi }
defaultRequest: { cpu: "250m", memory: 256Mi }
Terminal window
kubectl apply -f quota.yaml -f limitrange.yaml

A ResourceQuota counts every pod in the namespace, including the controller. To keep the math clean, run agents in a dedicated namespace so the quota only counts run pods.