Skip to content

feat: declare scheduled runs with the ScheduledRun CRD - #3241

Open
acherifi wants to merge 2 commits into
kagent-dev:mainfrom
acherifi:acherifi/Enable-schedules-to-be-configured-through-YAML
Open

acherifi wants to merge 2 commits into
kagent-dev:mainfrom
acherifi:acherifi/Enable-schedules-to-be-configured-through-YAML

Conversation

@acherifi

Copy link
Copy Markdown

Summary

Scheduled runs could only be created through the gRPC ScheduledRunService. This adds an api.kagent.dev/v1alpha3 ScheduledRun resource so they can be configured in YAML, following the same pattern as Agent and Harness.

apiVersion: api.kagent.dev/v1alpha3
kind: ScheduledRun
metadata: {name: nightly-report, namespace: kagent}
spec:
  agentRef: {name: assistant}
  schedule: "0 3 * * *"
  timeZone: Europe/Paris      # default UTC
  prompt: Summarize yesterday's incidents.
  paused: false               # default false
  executionTimeout: 15m       # default 15m

A reconciler persists each resource into the existing scheduled_run table. The existing Scheduler and execution Controller run it unchanged.

Design

  • Ownership: a resource has no human creator. Its row is owned by scheduledrun.api.kagent.dev/<namespace>/<name> and keyed by object UID. The creator-scoped gRPC API never shows declared schedules to human principals.
  • Status: Accepted (spec valid), ResolvedRefs (Agent exists; a missing Agent does not block persistence, matching gRPC updates), Ready (persisted for the current generation).
  • Edits: converge onto the same schedule. Prompt edits do not skip a due occurrence. Invalid edits keep the last accepted schedule running.
  • agentRef is immutable: reserved executions resolve the schedule's Agent when their session is reserved, so an in-place change could send an earlier prompt to another Agent. It is enforced by CEL and by the store, the same as gRPC, where the Agent is fixed at creation.
  • Deletion: the kagent.dev/scheduledrun-retirement finalizer retires the row, including deletions while the controller is down. Recreating an object starts a new schedule identity; history is retained.

Changes

  • CRD type plus generated deepcopy, clientset and Helm CRD.
  • go/api/scheduledrun: KubernetesCreator, Validate, RequestHash (the service now uses the shared hash).
  • Store: ApplyScheduledRun, RetireScheduledRuns; Create/Delete reuse the shared insert and tombstone helpers.
  • Reconciler in go/core/internal/controller/scheduledrun, wired in app.go.
  • Helm RBAC for scheduledruns, /status, /finalizers.
  • Docs: "Declared schedules" in docs/architecture/configuration-and-compilation.md.

Testing

  • PostgreSQL store test: create, no-op re-apply, edit, pause, rejected agent change (a reserved firing keeps its original Agent), recreate, retire, other owners untouched. TestInlineSQLPrepares passes.
  • Reconciler unit tests: persistence, defaults, missing Agent, invalid spec, store failure, finalizer add, delete-while-down, finalizer kept on retirement failure.
  • envtest admission cases, including agentRef immutability.
  • helm unittest rbac suite, golangci-lint, kube-api-linter: clean.
  • New e2e TestScheduledRunResource is not yet run; it needs a cluster.
  • TestSessionExpirationRechecksActivity fails intermittently on unmodified main as well; unrelated to this change.

Add an api.kagent.dev/v1alpha3 ScheduledRun resource so schedules can be
configured in YAML like Agents and Harnesses. A controller-runtime
reconciler persists each resource into the existing scheduled_run table
under a synthetic creator (scheduledrun.api.kagent.dev/<ns>/<name>) keyed
by object UID; the existing Scheduler and execution Controller run it.

- Admission validates five-field cron, prompt, timeout bounds; agentRef
  is immutable so reserved executions cannot be redirected.
- Status reports Accepted, ResolvedRefs, and Ready conditions.
- A finalizer retires the row on deletion, including deletions that
  happen while the controller is down.
- Store gains ApplyScheduledRun and RetireScheduledRuns; recreated
  objects tombstone the previous row and keep its execution history.
- Helm RBAC, admission, store, reconciler, and e2e tests, and docs.

Signed-off-by: acherifi <ali.cherifi@homagames.com>
@acherifi
acherifi requested a review from a team as a code owner October 16, 2026 08:21
@github-actions github-actions Bot added the enhancement New feature or request label Oct 16, 2026
Signed-off-by: acherifi <ali.cherifi@homagames.com>
// The last accepted schedule, if any, keeps running: deletion is final per
// request, so an invalid edit must not retire the row.
condition(v1alpha3.ScheduledRunConditionAccepted, metav1.ConditionFalse, "InvalidSchedule", err.Error())
condition(v1alpha3.ScheduledRunConditionReady, metav1.ConditionFalse, "NotAccepted", "The schedule was not accepted")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ready=false here, but the old schedule keeps firing

// TestScheduledRunResource declares a schedule in Kubernetes and verifies the
// controller persists it under the resource's own creator, runs it, follows
// edits, and retires it on deletion.
func TestScheduledRunResource(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has this e2e test run anywhere yet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants