Skip to content

feat(dispatcher): periodic recommender cron + persistent repo registry#142

Merged
thejustinwalsh merged 2 commits into
mainfrom
feat/recommender-cron
May 25, 2026
Merged

feat(dispatcher): periodic recommender cron + persistent repo registry#142
thejustinwalsh merged 2 commits into
mainfrom
feat/recommender-cron

Conversation

@thejustinwalsh

@thejustinwalsh thejustinwalsh commented May 25, 2026

Copy link
Copy Markdown
Owner

Closes #135.

What

The last piece for mm start + walk-away: the recommender now runs on a schedule for every managed repo, and the daemon's repo registry is persistent (survives restart, and is populated by mm init so a fresh repo is cron-eligible cold).

Before this, the recommender ran only via a manual /trigger/recommender, and the daemon's repoPaths was in-memory + empty at startup — so a cron would have had no repos to iterate.

How

  • Migration 004repo_config.checkout_path (nullable). A row with a non-null path is a "managed repo". repo_config already keyed per-repo state (paused_until, last_recommender_run); this adds the one missing fact: where the checkout lives.
  • Registry helpers (repo-config.ts) — registerManagedRepo (upsert, preserves pause/bookkeeping), getManagedRepoPath, listManagedRepos, getLastRecommenderRun, markRecommenderRun.
  • recommender-cron.ts — mirrors poller-cron/watchdog-cron. Ticks every minute as a due-check: for each managed, recommender-enabled, unpaused repo whose last_recommender_run is older than its interval_minutes, it stamps the run time BEFORE firing (so an overlapping/slow tick can't double-dispatch) and runs the recommender. enabled gates periodic running; auto_dispatch stays the separate downstream gate (so a repo can run ranking-only on a schedule).
  • main.ts — hydrates repoPaths from the registry on startup; rememberRepoPath persists every learned path (dispatch / trigger); extracts runRecommenderForRepo (shared by the /trigger/recommender route and the cron, so both behave identically); starts + tears down the cron.
  • mm init — registers the repo in the daemon DB via a registerRepo seam (the CLI entry wires it to a registerManagedRepo write), best-effort. A freshly-init'd repo joins the registry with zero priming.

How to verify

bun run typecheck && bun test && bun run lint && bun run format

What to verify

  • registry round-trips, is idempotent, preserves an existing pause, excludes pathless rows — repo-config.test.ts.
  • cron fires only due+enabled+unpaused repos; honors interval_minutes; non-positive interval never runs; stamps-before-firing so a throwing run isn't retried (and is isolated); ignores unmanaged rows — recommender-cron.test.ts.
  • mm init registers (slug, resolved path) on success, not on --dry-run, and a registry-write failure doesn't fail init — init-register.test.ts.
  • migration 004 applies (schema version → 4) — db.test.ts.
  • Full suite 605 pass, lint/typecheck/format clean.

Dogfood after merge

mm init /path/to/repo                 # now also registers it in the daemon DB
mm config /path/to/repo auto_dispatch true
# set [recommender] enabled = true, interval_minutes = N in .middle/config.toml
mm start                              # cron runs the recommender every interval; auto-dispatch follows

Review notes

  • mm init opens the daemon DB (openAndMigrate at the configured db_path) to write the registry row. WAL mode (already on) makes this safe alongside a running daemon; openAndMigrate is idempotent. Best-effort — wrapped so a write failure never fails init.
  • Cron deps use a narrow seam (loadRepoConfig + runRecommender) so the pass unit-tests with no engine/gh.

Summary by CodeRabbit

  • New Features

    • CLI init now best-effort registers initialized repos into the daemon’s managed-repo registry (dry-run unchanged; registration failures are non-fatal).
    • Daemon adds a recommender scheduler that periodically triggers recommender runs, skips paused/disabled repos or non-positive intervals, and prevents duplicate dispatches by timestamping before execution.
  • Chores

    • Database schema adds a nullable checkout path column for managed repos.
  • Tests

    • Added/updated tests for registration, cron scheduling, and DB migrations.

Review Change Stack

#135)

Closes the last gap to 'mm start' + walk-away: the recommender ran only on a
manual trigger, and the daemon's repo map was in-memory (empty at startup), so a
cron would have no repos to iterate.

- Migration 004: add repo_config.checkout_path — a row with a non-null path IS a
  'managed repo'. The persistent registry (repo_config already keyed per-repo).
- repo-config: registerManagedRepo / getManagedRepoPath / listManagedRepos +
  getLastRecommenderRun / markRecommenderRun.
- recommender-cron.ts (mirrors poller/watchdog crons): every minute, a due-check
  over managed + recommender-enabled + unpaused repos — stamps last_recommender_run
  BEFORE firing (no double-dispatch) and runs the recommender for any whose
  interval_minutes has elapsed. enabled gates periodic running; auto_dispatch is
  the separate downstream gate.
- main.ts: hydrate repoPaths from the registry on startup; persist on every
  learned path (dispatch / trigger) via rememberRepoPath; extract
  runRecommenderForRepo shared by the /trigger route and the cron; start + shut
  down the cron.
- mm init registers the repo (registerRepo seam → daemon db write), so a freshly-
  init'd repo is cron-eligible cold with zero priming.
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 43669c8c-07ff-4816-a7d5-1c87cc4bdd92

📥 Commits

Reviewing files that changed from the base of the PR and between bef619b and 1302f47.

📒 Files selected for processing (2)
  • packages/dispatcher/src/main.ts
  • packages/dispatcher/src/recommender-cron.ts

📝 Walkthrough

Walkthrough

Adds a durable managed-repo registry and migration, a recommender cron pass and scheduler, dispatcher hydration and wiring to persist checkout paths and run recommenders, and a CLI callback to register repos in the daemon after init.

Changes

Recommender Cron and Managed Repo Registry

Layer / File(s) Summary
Database schema and managed-repo registry API
packages/dispatcher/src/db/migrations/004_repo_checkout_path.sql, packages/dispatcher/src/repo-config.ts, packages/dispatcher/src/index.ts, packages/dispatcher/test/repo-config.test.ts, packages/dispatcher/test/db.test.ts
Adds nullable checkout_path column and ManagedRepo type; implements registerManagedRepo, getManagedRepoPath, listManagedRepos, getLastRecommenderRun, markRecommenderRun. Exposes these via dispatcher index; tests updated/added for registry behavior and migration version.
Recommender cron scheduler implementation
packages/dispatcher/src/recommender-cron.ts, packages/dispatcher/test/recommender-cron.test.ts
Adds runRecommenderCronPass (filters paused/disabled, computes interval, stamps last_recommender_run before run, isolates per-repo errors, returns fired count) and startRecommenderCron (Bunqueue-based scheduler). Tests cover scheduling, gating, pausing, disabled/missing configs, non-positive intervals, pre-stamp semantics, and unmanaged-row ignoring.
Dispatcher startup, hydration, and cron wiring
packages/dispatcher/src/main.ts
Hydrates in-memory repoPaths from DB, adds rememberRepoPath to persist checkout paths, extracts runRecommenderForRepo used by dashboard trigger and cron, wires cron to run per managed checkout path, and stops cron on shutdown.
CLI init callback to register repos in daemon
packages/cli/src/commands/init.ts, packages/cli/src/index.ts, packages/cli/test/init-register.test.ts
Adds optional registerRepo to InitCliOptions; runInit invokes it best-effort after non-dry-run init. Implements registerRepoInDaemonDb helper to open/migrate DB and call registerManagedRepo. Tests verify successful registration, dry-run skip, and graceful failure handling.

Sequence Diagram

sequenceDiagram
  participant Cron as Recommender Cron Pass
  participant DB as Dispatcher DB
  participant Dispatcher as Dispatcher (runRecommenderForRepo)
  participant Repo as Repo Checkout Path
  participant Recommender as dispatchRecommender
  Cron->>DB: listManagedRepos()
  DB-->>Cron: managed repos with checkout_path
  loop For each managed repo
    Cron->>Dispatcher: runRecommenderForRepo(checkoutPath)
    Dispatcher->>DB: markRecommenderRun(repo)  -- stamp before run
    Dispatcher->>Recommender: dispatchRecommender(repo, triggerAutoDispatch)
    Recommender->>Dispatcher: triggerAutoDispatch -> scheduleAutoDispatch(repo)
  end
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs:

Suggested labels:
ready-for-review

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding a periodic recommender cron and a persistent daemon repo registry, which are the primary objectives of the PR.
Linked Issues check ✅ Passed All primary coding requirements from issue #135 are implemented: persistent daemon repo registry with checkout paths, cron implementation that respects interval_minutes and paused state, and mm init repo registration.
Out of Scope Changes check ✅ Passed All changes align with issue #135 scope: registry helpers, recommender cron, main.ts hydration/persistence logic, init registration, migrations, and comprehensive tests. No unrelated refactoring or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@thejustinwalsh

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dispatcher/src/main.ts`:
- Around line 450-452: The cron callback currently ignores the result of
runRecommenderForRepo; change runRecommender to await the returned result (e.g.,
const result = await runRecommenderForRepo(checkoutPath)) and check its status
field, throwing or returning an error when result.status !== 202 so non-202
launches (400/500/etc.) propagate as failures; reference runRecommender and
runRecommenderForRepo when making this change and include the response body in
the thrown error/log for debugging.

In `@packages/dispatcher/src/recommender-cron.ts`:
- Around line 22-33: Add a TSDoc/JSDoc comment above the exported
RecommenderCronDeps type describing its purpose and usage (it represents the
dependencies required by the recommender cron job), then keep the existing
inline field comments for db, loadRepoConfig, runRecommender, and now; ensure
the top-level comment appears immediately before the RecommenderCronDeps
declaration so the module export rule recognizes it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 829afd41-0e0c-4888-bb55-86a6aaba4556

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6d782 and bef619b.

📒 Files selected for processing (11)
  • packages/cli/src/commands/init.ts
  • packages/cli/src/index.ts
  • packages/cli/test/init-register.test.ts
  • packages/dispatcher/src/db/migrations/004_repo_checkout_path.sql
  • packages/dispatcher/src/index.ts
  • packages/dispatcher/src/main.ts
  • packages/dispatcher/src/recommender-cron.ts
  • packages/dispatcher/src/repo-config.ts
  • packages/dispatcher/test/db.test.ts
  • packages/dispatcher/test/recommender-cron.test.ts
  • packages/dispatcher/test/repo-config.test.ts

Comment thread packages/dispatcher/src/main.ts
Comment thread packages/dispatcher/src/recommender-cron.ts
…ronDeps

CodeRabbit on #142:
- the cron's runRecommender ignored runRecommenderForRepo's {status,body}, so a
  non-202 (bad config / unresolvable repo) counted as a fired run. Throw on
  non-202 so the per-repo catch logs it and doesn't count it; the repo stays
  stamped (retries next interval, no spin).
- add the missing top-level TSDoc block on the exported RecommenderCronDeps.
@thejustinwalsh

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Periodic recommender scheduling (cron) + daemon repo registry

1 participant