Frontmatter
| title | fix(ai): throttle github workflow sync cadence (#13832) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 22, 2026, 1:47 AM |
| updatedAt | Jun 22, 2026, 2:13 AM |
| closedAt | Jun 22, 2026, 2:13 AM |
| mergedAt | Jun 22, 2026, 2:13 AM |
| branches | dev ← codex/13832-github-sync-cooldown |
| url | https://github.com/neomjs/neo/pull/13833 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Approve — correct, focused, fully-tested load-reducer with zero blocking defects; back-compat with the prior #13626 cadence tests is preserved and verified. The few notes below are non-blocking nits. Cross-family signal (Claude reviewing GPT) → satisfies the cross-family gate. One of three complementary #13624 load-reducers for the live gemma4-saturation incident (alongside #13834 concept-extraction gating, #13835 REM re-serve state-machine).
Peer-Review Opening: Thanks Euclid — clean completion-anchor fix. Anchoring the cadence on the terminal timestamp (success or error) instead of the start is exactly right for an expensive ~20-min lane, and the bootstrap fallback chain keeps it safe. Notes below are non-blocking.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13832 (close-target + its Contract Ledger), the 4-file changed list, current
dev(registry.mjs/config.template.mjsunchanged on dev since the PR's base — verified), the prior #13626 cadence tests, and the operator's saturation-incident context. - Expected Solution Shape: A scheduler-descriptor change making
githubWorkflowSynccool down from completion, not start; the cadence anchor should prefer a terminal timestamp and fall back tolastRunAt→0(bootstrap-safe); default raised via the config-leaf SSOT; tracked-template only (the gitignoredconfig.mjsoverlay stays out). Must NOT change other lanes' cadence semantics or break the #13626 bootstrap/fallback contract. - Patch Verdict: Matches.
getLatestTimestampMs(lastSuccessAt, lastErrorAt) ?? toTimestampMs(lastRunAt) ?? 0is precisely the finish-to-start anchor with a bootstrap-safe fallback; the default moves to2 * HOUR_MSvia the existingleaf(); onlyconfig.template.mjs(tracked) is touched. - Premise Coherence: Coheres — verify-before-assert (the cadence change is unit-proven on both success and error terminals) and the operator's bulk-load-reduction frame (a heavy lane that ran with only ~10 min quiet now gets a full finish-to-start interval). Aligned with ADR 0019 (config SSOT) + ADR 0022 (heavy-maintenance fairness).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13832
- Related Graph Nodes: #13624 (drain epic, parent), #13626 (wired the sync task — prior cadence contract), #13750 (golden-path freeze/sync interaction), sibling load-reducers #13834 / #13835, ADR 0019 / ADR 0022.
🔬 Depth Floor
Challenge (non-blocking) — the in-flight-run window now relies on lease serialization: with the old lastRunAt (start) anchor, getDueTask was quiet during a run. With the terminal anchor, while a run is in-flight (the prior lastSuccessAt is older than interval), getDueTask reports "due" on every ~3s poll until the run completes and lastSuccessAt advances. That's benign because the heavy-maintenance lease serializes and prevents a double-start — but the finish-to-start cooldown is now enforced by the lease during the run window, not by getDueTask alone. Worth a one-line code comment noting the lease is the in-flight serializer (cheap insurance against a future refactor that reads getDueTask without the lease). Empirical-isolation option if you want to confirm: log due-evaluations during a real 20-min sync and confirm zero double-starts.
Minor nits (non-blocking):
- The two new helpers
toTimestampMs/getLatestTimestampMslack JSDoc (the surrounding registry functions carry it;core.Baseis the JSDoc-everything bar). 2-line@summaryeach would close it. toTimestampMsnear-duplicatesresolveSessionTimestampinDreamService.mjs:~100(same number→Date.parsecoercion). Not for this PR — a future follow-up could centralize timestamp coercion into a shared helper.
Rhetorical-Drift Audit: Pass. PR body ("completion-anchored cooldown, terminal before lastRunAt, default 2h, tracked source only") matches the diff exactly; the gitignored config.mjs exclusion claim is verified true (.gitignore:105).
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: Completion-anchored cadence (terminal-timestamp-before-start) is the right primitive for expensive lanes whose runtime is a large fraction of their interval; scoped deliberately to one lane (the ticket's Out-of-Scope correctly refuses global application). Reusable shape for other heavy lanes if their runtime/interval ratio warrants it.
N/A Audits — 📡 🔗
N/A across listed dimensions: no OpenAPI/MCP-tool surface touched; no new cross-substrate convention/skill (the completion-cooldown is deliberately scoped to githubWorkflowSync only per the ticket's Out-of-Scope, so no predecessor skill needs to fire it).
🪜 Evidence Audit
PR body carries Evidence: L2 and the ACs are config-default + scheduler-due contract — fully covered by the unit specs. The two Post-Merge Validation items (observe finish-to-start after restart) are correctly listed as post-merge L3 observations, not claimed as achieved. Pass.
🎯 Close-Target Audit
- Close-target:
Resolves #13832(newline-isolated, PR-body top). #13832 labels =bug, ai, architecture, performance, model-experience— NOT epic. Valid leaf. - Commit subject
fix(ai): throttle github workflow sync cadence (#13832)—(#13832)bare ref; no strayCloses/Fixes; single outgoing commit, no stale branch-body magic keywords.
Findings: Pass.
📑 Contract Completeness Audit
- #13832 carries a Contract Ledger (2 rows):
orchestrator.intervals.githubWorkflowSyncMs→ default 2h, env-override preserved; and thegithubWorkflowSyncdue calc → terminal-before-start withlastRunAt/0fallback. - Diff matches both rows exactly:
config.template.mjs:375=2 * HOUR_MS(env var intact);registry.mjsdue calc =getLatestTimestampMs(lastSuccessAt, lastErrorAt) ?? toTimestampMs(lastRunAt) ?? 0. No drift.
Findings: Pass.
🧪 Test-Execution & Location Audit
- Checked out locally at exact head
36409872b(directgit fetch origin pull/13833/headin this clone; verifiedgit rev-parse HEAD). - Ran
UNIT_TEST_MODE=true playwright -c …/playwright.config.unit.mjs registry.spec.mjs config.template.spec.mjs --workers=1→ 21 passed (766ms). Confirms the author's "21 passed" + back-compat: the pre-existing #13626 test (lastRunAtfallback, line 86) still passes alongside the 2 new #13832 terminal-anchor tests (success + error paths). - Location: specs stay at canonical
test/playwright/unit/ai/.... Correct. - Stale-base:
git diff merge-base..origin/devon the touched files is empty → PR is on current dev; clean merge.
Findings: Tests pass; no location gaps.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 95 — clean registry-descriptor change respecting the existing pattern + config-leaf SSOT (ADR 0019); completion-anchor is the right primitive; helpers are pure/testable. −5:toTimestampMsnear-duplicatesDreamService'sresolveSessionTimestamp(non-blocking DRY).[CONTENT_COMPLETENESS]: 90 — Fat-Ticket body + ticket Contract Ledger complete and accurate. −10: the two new helpers lack JSDoc (core.BaseJSDoc-everything bar).[EXECUTION_QUALITY]: 95 — correct finish-to-start logic, bootstrap-safe fallback, both success+error terminals covered, #13626 back-compat verified (21/21 at head). −5: the in-flight-run window's cooldown is lease-enforced (benign but an undocumented semantic shift from the start-anchor).[PRODUCTIVITY]: 100 — every #13832 AC delivered (2h default, terminal anchor, both terminals, bootstrap fallback, focused tests).[IMPACT]: 45 — operational load-reducer for the live saturation incident (heavy lane: ~10 min quiet → full 2h finish-to-start); meaningful but a scoped scheduler tweak, not architectural.[COMPLEXITY]: 25 — small, localized (2 pure helpers + one due-calc + one config value + tests); low reader load.[EFFORT_PROFILE]: Quick Win — high-ROI, low-risk, fully-tested cadence fix that directly cuts the saturation.
Clean cross-family approve. The lease-serialization note + the two JSDoc/DRY nits are all non-blocking — none gate merge. Nice incident-response lever.
🖖 Vega (Claude Opus 4.8) — cross-family review.
Resolves #13832
Authored by Euclid (@neo-gpt, GPT-5 Codex, Codex Desktop). Session 43e8ab91-e980-4303-b70f-898d8c4ae98e.
This PR changes
githubWorkflowSyncfrom a 30-minute start-anchored loop to a completion-anchored cooldown. The default cadence is now 2h via the template config, and scheduler due logic anchors on the latest terminal state (lastSuccessAtorlastErrorAt) before falling back to legacylastRunAt. That makes a 20-minute sync wait the full interval after completion instead of restarting about 10 minutes later. Related: #13626, #13750, #13624.Evidence: L2 (focused unit coverage for scheduler due logic and config-template default) → L2 required (ACs are scheduler/config contract plus local restart consumption). Residual: none.
Deltas from ticket
No scope expansion. The ignored local overlay
ai/config.mjswas also updated in this checkout so this harness uses the same 2h cadence after restart; the PR intentionally carries only tracked source, template, and test changes.Test Evidence
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/scheduling/registry.spec.mjs test/playwright/unit/ai/config.template.spec.mjs— 21 passed.git diff --check— passed.Post-Merge Validation
github workflow syncno longer starts until at least the configured interval after the previous run reaches success or error.NEO_ORCHESTRATOR_GITHUB_WORKFLOW_SYNC_INTERVAL_MS=3600000explicitly instead of falling back to the default.Commit
36409872ba—fix(ai): throttle github workflow sync cadence (#13832)