Skip to content

AgentSpawner CRD

Group/Version: agents.re-cinq.com/v1beta1 · Kind: AgentSpawner · Scope: Namespaced · Short name: asp

Creates an Agent in a Station on a cron schedule, for each matching signed GitHub webhook delivery, or both. It has a spec you write and a status the controller writes. The kind was added at v1beta1 and is not served at v1alpha1. For walkthroughs, see Schedule runs and Trigger runs from GitHub.

kubectl get asp prints the Schedule, Station, Suspend, Last Schedule and Age columns.

FieldTypeDefaultNotes
stationRefstringrequiredThe Station each spawned Agent runs in. A DNS-1123 name.
schedulestringnoneFive-field cron expression, evaluated in UTC. See Schedule syntax.
githubobjectnoneStarts a run for each matching signed GitHub webhook delivery. See spec.github.
parametersmap[string]string{}Copied as-is into every spawned Agent’s spec.parameters; they fill the recipe’s {name} prompt tokens.
concurrencyPolicyenumAllowAllow starts a run on every tick. Forbid skips a tick while an Agent this spawner created is still Pending or Running. An unknown value is treated as Allow.
suspendboolfalsetrue stops new runs, scheduled or from webhooks. Runs already started are not touched.

A spawner needs schedule, github, or both. The API server rejects one with neither (set schedule, github, or both).

Needs the webhooks component.

FieldTypeDefaultNotes
secretRefstringrequiredKey in the agent-secrets Secret holding the webhook’s HMAC secret.
events[]stringrequiredDeliveries that start a run, as <X-GitHub-Event>.<action> (issues.opened, issues.labeled). An event without an action is listed by its name alone.
labelstringnoneOnly issues carrying this label start a run. For issues.labeled, only when it is the label just added.
repositorystringnoneowner/name. Only deliveries whose repository.full_name equals it (ignoring case) start a run.

The receiver serves POST /v1/namespaces/<namespace>/agentspawners/<name>/github on port 8083. It reads the spawner, then the HMAC key, and checks X-Hub-Signature-256 (sha256= and the hex HMAC-SHA256 of the raw body) in constant time before it reads the payload. A matching delivery creates an Agent named <spawner>-<X-GitHub-Delivery>, labelled agents.re-cinq.com/github-delivery=<delivery id> as well as with the spawner, with targetRepo set to the repository and these parameters over the spawner’s own:

ParameterFrom the payload
issue_numberissue.number
issue_titleissue.title
issue_bodyissue.body (empty when null)
issue_urlissue.html_url
reporepository.full_name

A redelivery of the same delivery finds its Agent already there and starts nothing. The response codes are listed in the task guide. concurrencyPolicy applies to scheduled runs only.

FieldTypeNotes
lastScheduleTimedate-timeThe schedule time of the last tick the controller handled, whether it created an Agent or skipped it under Forbid. This is the slot time (for example 03:00:00Z), not when the Agent was created.
lastAgentstringName of the last Agent this spawner created.

Five fields separated by spaces: minute (0-59), hour (0-23), day of month (1-31), month (1-12) and day of week (0-7, where both 0 and 7 are Sunday). Each field is one of:

FormExampleMeaning
**Every value.
number5That value.
range9-17Every value from 9 to 17.
step*/15, 9-17/2, 5/15Every 15th value; every 2nd value from 9 to 17; every 15th value from 5 to the field’s maximum.
list0,30Any of the comma-separated items, each of which can be any form above.

When both day of month and day of week are restricted (neither starts with *), a day that matches either one fires. 0 9 1 * 1 runs at 09:00 on the 1st of each month and on every Monday.

Names (jan, mon), @daily-style macros, ?, L and W are not supported. The CRD schema only checks that there are five fields. The controller rejects any other bad value when it reads the spawner: it logs agent spawner <name>: cron <field> "<value>": ... and skips that spawner until the schedule is fixed. A schedule that can never fire (0 0 30 2 *) is logged as cron schedule never fires.

This section covers schedule; webhook deliveries are answered by every replica, as they arrive.

Only the leader replica runs schedules. Every 15 seconds it lists the AgentSpawners in its namespace. A spawner without a schedule is skipped. For each one it takes the latest schedule time at or before now. If that is later than status.lastScheduleTime (or than the spawner’s creationTimestamp when it has never fired), the spawner is due:

  1. Under Forbid, it lists the Agents with the label agents.re-cinq.com/spawner=<name>. If any is Pending or Running, it records the slot in status.lastScheduleTime and creates nothing.
  2. Otherwise it creates the Agent and then sets status.lastScheduleTime and status.lastAgent.

A run starts at most 15 seconds after its schedule time. A new spawner first fires at the first schedule time after it was created, not at once.

The spawned Agent looks like this (for the slot 2026-10-17T03:00:00Z):

apiVersion: agents.re-cinq.com/v1beta1
kind: Agent
metadata:
name: nightly-story-29870100 # <spawner>-<slot in minutes since the Unix epoch>
namespace: ai-agents
labels:
agents.re-cinq.com/spawner: nightly-story
ownerReferences:
- apiVersion: agents.re-cinq.com/v1beta1
kind: AgentSpawner
name: nightly-story
controller: true
blockOwnerDeletion: true
spec:
stationRef: story-writer
parameters: { title: "The Night Shift" }
  • The name comes from the slot. A second create for the same slot is rejected by the API server as already existing, so the slot still gets one run. Names longer than 63 characters are shortened with a hash.
  • Deleting the spawner deletes its Agents (and their Jobs) through the owner reference.
  • History is pruned by the Station’s successfulRunsHistoryLimit and failedRunsHistoryLimit, the same as any other Agent of that Station. The spawner has no limit of its own.

The last slot handled is stored in the spawner’s status, not in the controller’s memory. A new leader reads it and carries on: a slot already handled is not run again, and a slot that came due while no replica was leader is run when the next one takes over.

After an outage that missed several slots, the spawner runs once, for the latest missed slot, and does not replay the others. An hourly spawner that comes back at 13:20 after missing 11:00, 12:00 and 13:00 creates one Agent, for 13:00.

If the controller stops between creating the Agent and writing the status, the next leader tries the same slot again. The create is rejected because the Agent already exists, and the status is then written.

For scheduled runs, parameters are copied as they are, including the reserved git_credential and git_credential_url keys the run uses for brokered git access (see Agent runtime). Runs started by a webhook never carry those two keys: they are dropped from the spawner’s parameters, and the payload fills only the five issue parameters. A spawner is written by someone who can create Agents anyway, so this grants nothing new. A run credential is usually short-lived, though, and putting one in a spawner stores it in the cluster in plain text and reuses it on every run. Leave those keys out of a spawner unless the credential is meant to be long-lived.

The controller role has create on agents, get/list/watch on agentspawners and patch on agentspawners/status. The webhooks component adds get on the agent-secrets Secret, to read the HMAC keys. The agent-launcher caller role can create, read, update and delete AgentSpawners. See RBAC & network.

@re-cinq/agent-contracts exports the AgentSpawner, AgentSpawnerSpec, GitHubTrigger, AgentSpawnerStatus and SpawnerConcurrencyPolicy types. The Go module has the same types plus AgentSpawnerList, registered by AddToScheme.