recurring-task-creator

command module
v0.11.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: BSD-2-Clause Imports: 26 Imported by: 0

README

Recurring Task Creator

Go Reference CI Go Report Card Ask DeepWiki

Publishes task.CreateCommand events to Kafka on a fixed schedule so a downstream task-controller materializes recurring tasks (daily / weekly / weekday / monthly / quarterly / yearly) as Obsidian vault .md files.

Task definitions live as Kubernetes Schedule Custom Resources — adding / editing / removing a recurring task is kubectl apply, not a code release.

How it works

kubectl apply -f schedule.yaml          ← operator
        │
        ▼
   K8s API server  (Schedule CRD, group task.benjamin-borbe.de/v1)
        │ watch
        ▼
   recurring-task-creator pod  ← installs the CRD at boot,
                                 runs an informer in its own pod
                                 namespace, hourly tick reads from
                                 the cache
        │ task.CreateCommand
        ▼
   Kafka  ← topic per vault (e.g. task-command-personal)
        │
        ▼
   agent-task-controller pod  ← consumes the topic,
                                writes the markdown file
        │
        ▼
   Obsidian vault git remote  ← committed by the controller

Identifiers are deterministic: UUID5("recurring-<slug>-<period-token>"), where the period token is YYYY-MM-DD (daily), YYYYWww (weekly), YYYYWww-<3-letter-weekday> (weekday), YYYY-MM (monthly), YYYYQN (quarterly), YYYY (yearly). The downstream controller dedups on identifier → safe to re-publish every tick, safe to manual /trigger?date=YYYY-MM-DD replay, safe to crash-restart.

Define a schedule

See k8s/apis/task.benjamin-borbe.de/v1/testdata/example.yaml for the canonical shape:

apiVersion: task.benjamin-borbe.de/v1
kind: Schedule
metadata:
  name: weekly-review              # frozen — UUID5 input
  namespace: default               # the recurring-task-creator pod watches its own namespace
spec:
  vault: default                   # routes to agent-task-controller-<vault>
  title: Weekly Review {{current_week}}   # placeholder-rendered; period token also suffixed
  schedule:
    recurrence: Weekday            # Daily | Weekly | Weekday | Monthly | Quarterly | Yearly
    weekday: Saturday              # required iff recurrence == Weekday (Monday..Sunday)
    periodOffset: 0                # optional, default 0; shifts period-anchored token by N periods (Monthly/Quarterly/Yearly only). Use -1 for "review the just-completed period" schedules.
  template:
    body: |
      Reflect on the past week ({{current_week}}).
      Plan the next ({{next_week}}).
    frontmatter:                   # YAML frontmatter stamped onto the generated task file
      priority: 2
      status: in_progress
      page_type: task
      assignee: bborbe
      planned_date: "{{current_date}}"   # placeholder-rendered in string values
      due_date: "{{next_sun_date}}"

The CRD's OpenAPI schema enforces the recurrence enum, the weekday enum, and two CEL rules: weekday is required iff recurrence == "Weekday", and non-zero periodOffset is allowed only for Monthly / Quarterly / Yearly.

periodOffset

spec.schedule.periodOffset (int, default 0) shifts the period-anchored token by N periods. The shift applies to both the title suffix and the UUID5 identifier input, so re-publishing the same (slug, fire-date, offset) triple stays idempotent. Body placeholders ({{current_month}}, {{last_month}}, etc.) are NOT affected — they always render against the unshifted fire date.

The motivating case is review-style schedules. A monthly-review schedule with periodOffset: -1 is always-fire (fires every day), but every tick names the prior month: on 2026-07-01 the publisher emits identifier UUID5(...) for token 2026-06, materializing the task Review Month - 2026-06.md. The same identifier is re-published every day in July (dedups), then rolls to 2026-07 on 2026-08-01. The body's {{last_month}} placeholder still renders the correct prior month relative to the fire date — title and content agree.

Quarterly uses ±3 months; yearly uses ±N years. Negative values name prior periods; positive values name future periods. Cross-year/quarter boundaries are handled by Go's time.AddDate semantics.

Template placeholders

Substituted in title, body, and any string-valued frontmatter field. Non-string frontmatter values (ints, slices, maps) pass through unchanged. Closed set — unknown tokens like {{foo}} render verbatim. All values are computed against the Berlin civil date the task fires for.

Placeholder Renders Example (Sat 2026-06-20)
{{current_date}} YYYY-MM-DD 2026-06-20
{{next_sat_date}} YYYY-MM-DD (today if today IS Sat) 2026-06-20
{{next_sun_date}} YYYY-MM-DD (today if today IS Sun) 2026-06-21
{{current_week}} YYYYWNN (ISO) 2026W25
{{next_week}} YYYYWNN +7d 2026W26
{{current_month}} YYYY-MM 2026-06
{{next_month}} YYYY-MM +1mo 2026-07
{{last_month}} YYYY-MM −1mo 2026-05
{{current_quarter}} YYYYQN 2026Q2
{{last_quarter}} YYYYQN −1q 2026Q1
{{current_year}} YYYY 2026
{{next_year}} YYYY +1y 2027
{{last_year}} YYYY −1y 2025

{{next_sat_date}} / {{next_sun_date}} use inclusive-today semantics so a Sunday Schedule firing on Sun stamps planned_date=<today>, not <today+7>.

Removed in v0.3.0: the pre-v0.2.0 kebab-case alias names ({{date}}, {{iso-week}}, {{next-iso-week}}, {{month}}, {{last-month}}, {{quarter}}, {{last-quarter}}, {{year}}, {{last-year}}) no longer render. Use the canonical snake_case names above.

Build + deploy

make test          # ginkgo specs + coverage
make buca          # build + push docker.io/bborbe/recurring-task-creator:$(VERSION) (publish-only)
make helm-publish  # package + push the Helm chart to oci://registry-1.docker.io/bborbe

make buca is publish-only (build image + push); it applies nothing to a cluster. Deployment is the Helm chart in helm/ — a StatefulSet, Service, Role + RoleBinding (namespace-scoped Schedule watch), ClusterRole + ClusterRoleBinding (boot-time CRD self-install), and optional Strimzi KafkaUser + Sentry Secret. Per-cluster config (Kafka brokers, stage, topicPrefix, Sentry, node affinity, pull secrets) is supplied by the consuming values — the quant deployment lives in the private bborbe/quant config repo.

topicPrefix must match the downstream controller. The service publishes to <topicPrefix>-agent-task-v1-request (empty = unprefixed). Set topicPrefix to whatever the agent-task-controller consuming the topic uses (e.g. develop / master), or the tasks land on a topic nobody reads.

Schedule CRs themselves live in a separate (typically private) repository — they reference the deployed image but are operator-owned data, not part of the binary. The repo deployer applies them with kubectl apply -k <overlay> (kustomize) or any GitOps mechanism of choice.

RBAC

The pod's ServiceAccount needs:

  • apiextensions.k8s.io/customresourcedefinitions get / create / update / patch at cluster scope — for the boot-time CRD install
  • task.benjamin-borbe.de/schedules get / list / watch in the pod's own namespace — for the informer

Manifests: helm/templates/rbac.yaml (ServiceAccount + Role/RoleBinding + ClusterRole/ClusterRoleBinding). The ClusterRole is namespace-suffixed so dev + prod installs don't collide on the cluster-scoped object.

Local smoke-test

DRY_RUN=true ./recurring-task-creator-run-once -logtostderr -v=2

Skips Kafka init entirely (uses a noop sender); logs every (slug, date, identifier) triple the publisher would send. Use to verify a Schedule's behavior before deploying.

Repo layout

Path Purpose
main.go + cmd/run-once/main.go Long-lived service entry point + one-tick smoke binary
pkg/k8s_connector.go Self-installing CRD (SetupCustomResourceDefinition) — get-or-create-or-update on every binary boot, 30s timeout
pkg/k8s_connector_schema.go The Go-built JSONSchemaProps (vault regex, recurrence enum, weekday enum, CEL rule)
pkg/store Informer-backed ScheduleStore — the only source of truth at tick time
pkg/publisher Builds task.CreateCommand, computes the period-anchored UUID5, sends via injected sender
pkg/tick Hourly cron loop + Prometheus metrics
pkg/handler HTTP — /healthz, /trigger?date=YYYY-MM-DD, /setloglevel/{n}, /metrics
pkg/factory Composition root — Create* constructors that wire everything
pkg/schedule Internal types — Date, RecurrenceKind, TaskDefinition, TasksForDate
k8s/apis/task.benjamin-borbe.de/v1/ CRD Go types (hand-written) + zz_generated.deepcopy.go
k8s/client/ Generated typed clientset + informers + listers + applyconfiguration
helm/ Standalone Helm deploy chart (STS + RBAC + Service + optional KafkaUser/Secret)
hack/update-codegen.sh Wraps kube_codegen.sh for the client tree
mocks/ Counterfeiter mocks
specs/, prompts/ dark-factory development history (specs → prompts → daemon-executed code)

License

BSD-2-Clause. See LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
cmd
run-once command
Command recurring-task-creator-run-once runs a single tick (compute today's task set and publish each via Kafka) then exits.
Command recurring-task-creator-run-once runs a single tick (compute today's task set and publish each via Kafka) then exits.
k8s
apis/task.benjamin-borbe.de/v1
Package v1 is the v1 API for the Schedule custom resource definition.
Package v1 is the v1 API for the Schedule custom resource definition.
client/clientset/versioned/fake
This package has the automatically generated fake clientset.
This package has the automatically generated fake clientset.
client/clientset/versioned/scheme
This package contains the scheme of the automatically generated clientset.
This package contains the scheme of the automatically generated clientset.
client/clientset/versioned/typed/task.benjamin-borbe.de/v1
This package has the automatically generated typed clients.
This package has the automatically generated typed clients.
client/clientset/versioned/typed/task.benjamin-borbe.de/v1/fake
Package fake has the automatically generated clients.
Package fake has the automatically generated clients.
Code generated by counterfeiter.
Code generated by counterfeiter.
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL