feat(scheduler): KubernetesBackend with client-go for runtime CronJob CRUD (#162 part 2b)#170
Merged
initializ-mk merged 1 commit intoJun 15, 2026
Conversation
… CRUD (#162 part 2b) Second half of part 2 of the #162 stack. Builds on the ScheduleBackend interface + FileBackend refactor + manifest helpers shipped in part 2 (PR #169). Adds the real K8s runtime backend, dependency on k8s.io/client-go, and the forge.yaml-driven backend selection in the runner. forge-cli/runtime/scheduler_k8s_backend.go KubernetesBackend implements scheduler.Backend by delegating persistence + timing to the cluster's CronJob controller: - Start/Stop/Reload are no-ops (cluster owns timing) - Sync reconciles cluster CronJobs against declared yaml entries: create, update on drift, prune dropped yaml entries, PRESERVE LLM-sourced entries unconditionally - Set / Delete are gated by AllowDynamic (default false); yaml-sourced CronJobs cannot be deleted via direct Delete (Sync is the only removal path for declarative entries) - List filters by forge.agent.id label so unrelated CronJobs in the namespace don't appear in schedule_list output - History returns empty + warns once; the audit stream's schedule_fire/complete events are the canonical source - CronJobs are constructed in-memory to match the YAML the forge-core scheduler.CronJobYAML emits byte-for-byte, so runtime reconcile doesn't churn against forge package manifests (#162 part 3) - Round-trips Schedule <-> CronJob via labels (agent.id, schedule.id, schedule.source) and annotations (task, skill, channel, channel_target, run_count, last_status) - LastRun read from CronJob.Status.LastScheduleTime - NewKubernetesBackendWithClient testing seam accepts an explicit kubernetes.Interface (fake.Clientset in tests) forge-cli/runtime/runner.go Backend selection wired off forge.yaml scheduler.backend: - "kubernetes" — always K8s; errors at startup when not in-cluster (FORGE_IN_CLUSTER=true overrides for tests) - "file" — always FileBackend - "auto"/"" — K8s when in-cluster, file otherwise Drops the old syncYAMLSchedules helper now that the runner calls Backend.Sync(declaredSchedules()) for both modes. go.mod k8s.io/api, k8s.io/apimachinery, k8s.io/client-go @ v0.36.2. Tests (9 cases against fake.Clientset): - Sync creates CronJobs for declared entries with the expected labels + ConcurrencyPolicy=Forbid - Sync is idempotent (no churn on no-op re-run) - Sync updates on cron drift - Sync prunes yaml entries removed from the manifest - Sync preserves LLM-sourced entries on yaml-only re-Sync - Dynamic Set is gated by AllowDynamic with an actionable error referencing the config flag - Dynamic Delete of a yaml-sourced schedule is refused with an error pointing operators at the manifest - List filters by forge.agent.id (unrelated CronJobs in the namespace are not returned) - History returns empty + does not error Docs: - docs/deployment/scheduler-kubernetes.md gains the RBAC table, the annotation round-trip reference, and the "what's not in the K8s backend" section flagging schedule_history deferral + cross-namespace out-of-scope + token rotation as a follow-up. Refs #162
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second half of part 2 of the #162 stack. Stacked on top of #169 (`feat/issue-162-k8s-scheduler`) — the base is that branch, not main. After #169 merges I'll rebase this to main.
Adds the real `KubernetesBackend` that implements `scheduler.Backend` against `k8s.io/client-go`, plus the forge.yaml-driven backend selection in the runner. The cluster's CronJob controller takes over timing; the agent stays out of the tick loop.
Behavior
Backend selection
`runner.go` picks the backend off `forge.yaml`:
The previous `syncYAMLSchedules` helper is replaced by `Backend.Sync(declaredSchedules())` which works for both modes uniformly.
Round-trip Schedule ↔ CronJob
The in-memory CronJob the runtime builds is byte-equivalent to the YAML `scheduler.CronJobYAML` emits, so #162 part 3's manifest-applied resources don't churn against the runtime's reconcile loop.
Files
Test plan
Dependency footprint
Goreleaser binary size impact: ~+10 MB transitive, the standard cost for any K8s-aware Go binary. Forge-core stays free of client-go — the dependency is contained to forge-cli/runtime.
What's next
Part 3 wires `scheduler.CronJobYAML` into the `forge package` build pipeline so a packaged deploy produces:
Refs #162