schedule

package
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

application

Orchestrates domain work via a two-phase execution model.

How it works

Scheduler
  ├── Phase 1: Preflights (one-shot, all must pass)
  └── Phase 2: Workers (long-running interval tasks)

Scheduler runs phases sequentially. If any preflight fails, workers never start.

Planner owns the per-repository concept. It takes domain specs and multiplies them by the configured repositories, producing ready-to-schedule units with closures that capture tenant context (owner, name, cached repoID).

Planner also provides InfraClassifier() — a ClassifierFunc that checks apierr interfaces (Retryable, WaitHinted, ServicePressure) on errors returned by infrastructure clients. Scheduler passes it to Runner's Baseline.

Domain specs are clean — no error knowledge, no retry hints. Retry strategy is configured entirely via Baseline on Runner (Node/Service/Degraded policies).

Key types

Type File Role
Preflight contracts.go Interface for one-shot tasks
Worker contracts.go Interface for interval tasks
Planner planner.go Builds per-repo task units from domain specs
Scheduler schedule.go Executes preflight → worker phases
NewPreflights registry.go Registers all preflight implementations
NewWorkers registry.go Registers all worker implementations

Adding a new task

  1. Create a domain type that implements Preflight or Worker (return a PreflightSpec or WorkerSpec from TaskSpec()).
  2. Register it in registry.go — add as a parameter to NewPreflights() or NewWorkers().
  3. Add the constructor to internal/bindings.go Wire set.
  4. Run task generate.

Task naming

Names are formatted by Scheduler as {phase}:{resource}:{owner}/{name}:

preflight:repository-validator:thumbrise/autosolve
worker:issue-poller:thumbrise/autosolve
worker:issue-poller:thumbrise/otelext

Extension points: tenants

The current tenant is RepoTenant (owner + name + repoID) — all work revolves around repositories. But this is not a dogma.

RepoTenant is defined in domain/spec/tenants/ and passed to domain work functions via Planner closures. If a future task needs a different unit of work (e.g. an org-level tenant, a user-level tenant), the path is:

  1. Define a new tenant type in domain/spec/tenants/.
  2. Define a new spec type with Work func(ctx, YourTenant) error.
  3. Add a new method to Planner that iterates the appropriate config and builds units.
  4. Scheduler calls the new Planner method in the right phase.

Planner is the only place that knows how to map config → tenants → closures. Domain types never know how many tenants exist or where they come from.

Future: module system

The current architecture has an implicit pattern: a tenant acts as a gravity point for a cluster of related components — lifecycle phases (preflights, workers), domain logic, and DAL repositories. Today this cluster is RepoTenant with its validator, issue parser, and repository/issue stores.

This naturally evolves into a module system where each tenant type defines a self-contained module:

modules/
  repo/                        ← "repository" module
    tenant.go                  ← RepoTenant
    preflights.go              ← validator, migrations, etc.
    workers.go                 ← issue poller, comment poller, etc.
    dal/                       ← module-specific repositories
    module.go                  ← Module.Register(app)

A module would implement a single interface:

type Module interface {
    Preflights() []Preflight
    Workers() []Worker
}

The application loads modules explicitly:

modules := []Module{repo.New(), analytics.New(), notifications.New()}

This is the same pattern as Linux loadable modules, PHP extensions (ext-curl, ext-pdo), or Git subcommands — each module brings its own lifecycle, domain logic, and storage, and the application provides the execution framework.

Not implemented yet. The current flat structure works for the current scale. When a second tenant type appears, that's the signal to extract the module system.

Documentation

Index

Constants

This section is empty.

Variables

Functions

This section is empty.

Types

type GlobalWorker added in v0.18.0

type GlobalWorker interface {
	TaskSpec() spec.GlobalWorkerSpec
}

GlobalWorker is a long-running interval task not scoped to a repository. Implementations return a GlobalWorkerSpec describing work on shared resources.

Task naming convention: worker:{Resource} Examples:

  • worker:issue-explainer

func NewGlobalWorkers added in v0.18.0

func NewGlobalWorkers(
	issueExplainer *workers.IssueExplainer,
) []GlobalWorker

NewGlobalWorkers registers all global worker tasks (not multiplied per repository). Add new global workers here when extending the system.

type Planner

type Planner struct {
	// contains filtered or unexported fields
}

Planner builds per-repository task units from domain specs. It owns the per-repo concept: each repository in config produces a set of preflight and worker units.

func NewPlanner

func NewPlanner(
	cfg *config.Github,
	preflights []Preflight,
	workers []Worker,
	repoRepo *repositories.RepositoryRepository,
) *Planner

func (*Planner) InfraClassifier

func (p *Planner) InfraClassifier() longrun.ClassifierFunc

InfraClassifier returns a ClassifierFunc that checks apierr interfaces on errors returned by infrastructure clients.

Classification:

  • apierr.WaitHinted with positive WaitDuration → Service + explicit wait
  • apierr.ServicePressure → Service
  • apierr.Retryable → Service
  • unknown → nil (let baseline handle as Unknown/Degraded)

func (*Planner) Preflights

func (p *Planner) Preflights() []PreflightUnit

Preflights returns one-shot units for all repositories × all preflight specs.

func (*Planner) Workers

func (p *Planner) Workers() []WorkerUnit

Workers returns interval units for all repositories × all worker specs. Each unit caches the repository ID on first invocation to avoid repeated lookups.

type Preflight

type Preflight interface {
	TaskSpec() spec.PreflightSpec
}

Preflight is a one-shot task that must complete before workers start. Implementations return a PreflightSpec describing the work to be done per tenant.

Task naming convention: preflight:{Resource}:{owner}/{name} Examples:

  • preflight:repository-validator:thumbrise/autosolve

func NewPreflights

func NewPreflights(
	repoValidator *preflights.RepositoryValidator,
) []Preflight

NewPreflights registers all preflight tasks. Add new preflights here when extending the system.

type PreflightUnit

type PreflightUnit struct {
	Resource string
	Repo     config.Repository
	Work     longrun.WorkFunc
}

PreflightUnit is a ready-to-schedule one-shot task produced by Planner.

type Scheduler

type Scheduler struct {
	// contains filtered or unexported fields
}

Scheduler orchestrates execution in two phases:

  1. Preflights — one-shot tasks, all must pass before workers start.
  2. Workers — long-running interval tasks (per-repo and global).

Scheduler is generic — it doesn't know about repositories, GitHub, or issues. It only knows phases and task units provided by Planner and global workers.

func NewScheduler

func NewScheduler(planner *Planner, globalWorkers []GlobalWorker, logger *slog.Logger) *Scheduler

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context) error

type Worker

type Worker interface {
	TaskSpec() spec.WorkerSpec
}

Worker is a long-running interval task. Implementations return a WorkerSpec describing the work to be done per tenant.

Task naming convention: worker:{Resource}:{owner}/{name} Examples:

  • worker:issue-poller:thumbrise/autosolve
  • worker:comment-poller:thumbrise/otelext

func NewWorkers

func NewWorkers(
	issuePoller *workers.IssuePoller,
	outboxRelay *workers.OutboxRelay,
) []Worker

NewWorkers registers all per-repository worker tasks. Add new workers here when extending the system.

type WorkerUnit

type WorkerUnit struct {
	Resource string
	Repo     config.Repository
	Interval time.Duration
	Work     longrun.WorkFunc
}

WorkerUnit is a ready-to-schedule interval task produced by Planner.

Jump to

Keyboard shortcuts

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