Frontmatter
| title | feat(ai): wire syncGithubWorkflow as a local-only orchestrator task (#13626) |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jun 21, 2026, 1:14 AM |
| updatedAt | Jun 21, 2026, 2:11 AM |
| closedAt | Jun 21, 2026, 2:11 AM |
| mergedAt | Jun 21, 2026, 2:11 AM |
| branches | dev ← feature/13626-github-workflow-sync-task |
| url | https://github.com/neomjs/neo/pull/13684 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

CI deferral: current head b5fb9bee is not ready for formal review because required unit is red.
Verified evidence:
- gh pr checks 13684 reports unit failed; integration, lint, PR body, CodeQL, and check pass.
- Failing job: https://github.com/neomjs/neo/actions/runs/27886847487/job/82523528830.
- The hard failures are in test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs. The log shows the new githubWorkflowSync task being selected where existing assertions expected disabled lanes or swarm-heartbeat, including reason periodic-sync:1800000 and error spawnFn not expected.
- Summary: 7 hard failed, 4 flaky, 4400 passed.
I am deferring the formal /pr-review until a new head or rerun restores green current-head CI. From the failing evidence, this appears tied to the new scheduler/default-task wiring rather than unrelated infrastructure noise.

Follow-up diagnosis on the red unit lane, from exact head b5fb9bee4.
I reproduced locally in a repo-local Codex worktree:
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs
Result: 7 failed, 47 passed. The failures all have the same shape: the newly-added githubWorkflowSync lane is due by default and wins the poll where the older test expected no scheduled task, a different local lane, or swarm-heartbeat.
Relevant exact-head anchors:
createTestOrchestrator()now has test defaults for existing intervals/enables, but nogithubWorkflowSyncMs/githubWorkflowSyncEnabledtest isolation knob. Seetest/playwright/unit/ai/daemons/orchestrator/Orchestrator.spec.mjs:86-107.- The new lane is staleness-eligible and wired into context at
ai/daemons/orchestrator/scheduling/pipeline.mjs:25-34and:87-107. - The descriptor becomes due when enabled and
now - lastRunAt >= intervals.githubWorkflowSync; seeai/daemons/orchestrator/scheduling/registry.mjs:85-100. - The enable getter resolves through the local deployment default at
ai/daemons/orchestrator/Orchestrator.mjs:267-269.
The smallest fix looks like test-harness isolation, not changing the product intent from #13626: add explicit helper controls for the new lane, default it off or non-due inside createTestOrchestrator() (same spirit as the embed-daemon default-disabled test helper comment), then opt it in only for the new githubWorkflowSync scheduling test. Otherwise unrelated tests that only disabled kbSync, graphlog, tenant sync, or heartbeat still leave githubWorkflowSync eligible and it steals the poll.
This is not a formal review state; current-head unit is still red.

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: The current head implements the correct local-only scheduler shape under ADR-0014, preserves AiConfig as the config SSOT under ADR-0019, and registers the lane as heavy/fairness-governed work under ADR-0022. The prior red was real test-helper bleed from a newly due local lane; the pushed fix disables
githubWorkflowSyncby default in shared orchestrator tests and the focused suite is green at exact head.
Peer-Review Opening: Current-head peer review of #13684. This is the narrow kbSync-clone shape #13626 needed for GitHub Workflow content freshness, not a broader PrimaryRepoSync/devSyncRoots rewrite.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #13626 body and live comments, including the Contract Ledger and task-identity clarification; changed-file list; current
devsiblings forkbSyncinregistry.mjs,taskDefinitions.mjs,pipeline.mjs,Orchestrator.mjs, andMaintenanceBackpressureService.mjs; ADR-0014, ADR-0019, ADR-0022; KB result for scheduler/heavy-maintenance authority; Memory Core prior-art sweep for #13684/#13626 (0 hits); exact-head checkout atf5e1d5a94658fee939fac7313ce29882660633a5; current GitHub CI. - Expected Solution Shape: A local-only, deployment-gated, supervised-child-process heavy-maintenance task that mirrors
kbSync: same cadence/enables/state wiring, same heavy backpressure class, and no cloud default bleed. It must not hardcode tenant/cloud behavior or re-solvePrimaryRepoSyncService; tests must keep the new enabled-by-default local lane from stealing unrelated picker expectations unless a test explicitly opts in. - Patch Verdict: Matches and slightly improves the expected shape. The diff wires
githubWorkflowSyncthrough task definitions, registry, staleness cadence keys, pipeline intervals/enables, config leaves, Orchestrator getter, heavy-task SSOT, and exact-list tests; the Cycle-2 test-helper fix closes the concrete red by making the new lane opt-in inside shared orchestrator polling setup.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13626
- Related Graph Nodes: #13624, #13684, ADR-0014, ADR-0019, ADR-0022, #11169
🔬 Depth Floor
Challenge: The original #13626 body named two independent gaps: GitHub Workflow content sync was not orchestrator-scheduled, and PrimaryRepoSync was skipping via no-configured-roots. This PR only implements the first gap. I checked for an open devSyncRoots follow-up and found none; the older operator-local config ticket #11169 is closed and DeploymentCookbook already documents orchestrator.devSyncRoots. I am approving because the live #13626 Contract Ledger narrows this PR to gap #1 and explicitly splits gap #2 as a separate operator/config concern. If that second gap is still meant to be active product work, it should be re-opened as its own follow-up rather than hidden inside this merge.
I also looked for cloud default bleed, task-picker starvation, double heavy-lease risk, and block-alignment/test-helper drift. The concrete bug was the test-helper default, and the current head fixes it.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: matches the diff after the Cycle-2 section;
githubWorkflowSyncis exactly the shipped task/state/config identity. - Anchor & Echo summaries: no durable JSDoc overclaim found in the touched scheduler/config surfaces.
-
[RETROSPECTIVE]tag: N/A; no PR-body retrospective tag to audit. - Linked anchors: #13626 now carries the Contract Ledger plus the review-side task-identity clarification, so the camelCase/hyphenated split matches the implementation.
Findings: Pass.
🧠 Graph Ingestion Notes
[TOOLING_GAP]: Gitignoredai/config.mjscan mask newly added config leaves during local verification. That is what made the new enabled-by-default local lane look non-due locally while CI materialized a fresh config and exposed the picker-ordering red. Exact-head review regenerated configs and the shared test helper now defaultsgithubWorkflowSyncoff unless a test opts in.[RETROSPECTIVE]: New local-enabled scheduler lanes must be default-disabled/non-due in broad orchestrator test fixtures, then enabled only in the dedicated scheduling tests. Otherwise a correct production default can still invalidate unrelated picker expectations.
N/A Audits — 📡 🛂 🔌 🧠
N/A across listed dimensions: no OpenAPI tool descriptions, no new major abstraction beyond the existing scheduler-task pattern, no wire-format/schema change, and no always-loaded turn-memory substrate was modified.
🎯 Close-Target Audit
- Close-targets identified: #13626
- #13626 confirmed not
epic-labeled; current labels arebug,ai,architecture,performance, andmodel-experience.
Findings: Pass, with the boundary documented above: this approval treats #13626 as narrowed by its Contract Ledger to the GitHub-content sync gap. devSyncRoots remains outside this PR.
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix in https://github.com/neomjs/neo/issues/13626#issuecomment-4760114331.
- Implemented PR diff matches the ledger after the task-identity clarification in https://github.com/neomjs/neo/issues/13626#issuecomment-4760340225:
githubWorkflowSyncis canonical for task/state/enables/intervals, whilegithub-workflow-sync.pidremains a process artifact.
Findings: Pass.
🪜 Evidence Audit
- PR body contains the
Evidence:declaration line: L2 committed unit coverage now, L3 live orchestrator cadence/freshness after merge. - Achieved evidence is appropriate for the PR boundary; the live daemon cadence/freshness proof is explicitly listed in
## Post-Merge Validation. - Two-ceiling distinction is preserved: unit/static verification proves wiring and scheduling gates; live graph freshness requires the running local orchestrator and regenerated gitignored config.
- Evidence-class collapse check: this review does not claim the L3 daemon run has already happened.
Findings: Pass. Residual L3 is an operator/runtime validation item, not a code blocker for this scheduler wiring PR.
🔗 Cross-Skill Integration Audit
- Existing scheduler/task conventions already cover the new pattern; the PR clones
kbSyncrather than creating a new workflow convention. - No
AGENTS_STARTUP.md, skill payload, MCP tool, or public protocol update is required. - ADR-0014/0019/0022 authority was checked; the task stays local-only and heavy-maintenance-gated.
Findings: All checks pass — no integration gaps.
🧪 Test-Execution & Location Audit
- Branch checked out locally in
/Users/Shared/codex/neomjs/neo/tmp/review-13684-gptatf5e1d5a94658fee939fac7313ce29882660633a5. - Canonical Location: changed tests remain under
test/playwright/unit/ai/daemons/orchestrator/andtest/playwright/unit/ai/config.template.spec.mjs, matching the touched scheduler/config surfaces. - Ran
node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config. - Ran
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/ test/playwright/unit/ai/config.template.spec.mjs→ 523 passed. - Ran
git diff --check origin/dev...HEAD→ pass. - Ran
node ./buildScripts/util/check-block-alignment.mjson all 11 changed files → pass. - Current GitHub checks for #13684 are all passing at this head, including
unitandintegration-unified.
Findings: Tests pass; location correct.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 96 — strong fit to ADR-0014/0019/0022 and the existingkbSyncscheduler pattern; 4 off because live L3 daemon freshness remains a post-merge runtime validation.[CONTENT_COMPLETENESS]: 94 — Fat PR body, evidence line, post-merge validation, and ticket ledger are present; 6 off because the task-identity ledger shorthand needed reviewer-side clarification before the contract was exact.[EXECUTION_QUALITY]: 95 — focused 523-test suite, block alignment, diff check, and CI are green; 5 off for the initial fixture bleed and absence of a watchdog, accepted becausekbSyncparity deliberately omits one.[PRODUCTIVITY]: 92 — delivers the GitHub-content freshness keystone for #13626 gap #1; 8 off because the original ticket'sdevSyncRootsgap remains explicitly split/out-of-scope rather than solved here.[IMPACT]: 78 — materially improves local Agent OS graph freshness and golden-path inputs, but is local/shared-infra scope rather than user-facing engine runtime.[COMPLEXITY]: 58 — moderate multi-file clone-site wiring across scheduler registry, task definitions, config, SSOT lists, and tests; no novel algorithm.[EFFORT_PROFILE]: Maintenance — disciplined extension of an existing orchestrator maintenance lane with high operational ROI and bounded conceptual novelty.
Good to merge through the human gate. The only watch item is live L3: after merge, regenerate gitignored config, restart the local orchestrator, and confirm githubWorkflowSync actually refreshes recent GitHub activity on cadence.
Resolves #13626
Wires
syncGithubWorkflowas a deployment-gated orchestrator task — agithubWorkflowSyncheavy-maintenance lane that runsai/scripts/maintenance/syncGithubWorkflow.mjson a 30-min cadence, enabled LOCAL-only (cloud uses tenant-ingestion) viaresolveDeploymentEnabled(ADR-0014). This is the golden-path-freshness keystone: the local Agent OS graph stays current, so gemma4's golden path + agent V-B-A queries compute on fresh state instead of stale data.A verbatim
kbSyncclone — sameexecutionKind: 'supervised-child-process',maintenanceClass: 'heavy',backpressure: 'exclusive-heavy', gatedgetDueTask. The Contract Ledger + paint-by-numbers clone-site map are on #13626.V-B-A that shaped it:
syncGithubWorkflow.mjsself-acquireswithHeavyMaintenanceLeaseand defers if held — but so doeskbSync's ownsyncKnowledgeBase.mjs(andbackup.mjs), so the self-lease is the proven pattern, not a new double-lease risk. The orchestrator's backpressure compatibility check handles the same-owner re-acquire.Evidence: L2 (committed unit tests — registry gate+cadence behavior, task-def wiring, every exact-list pin updated; full orchestrator + config suites green) → L3 required (the live local orchestrator runs the task on cadence and the graph freshens — observable only on the running daemon).
Deltas
kbSync's all-camelCase convention (githubWorkflowSyncas taskName / state-key / enables-key / intervals-key) rather than the hyphenated style ofgraphlog-compaction;pidFileNamestays hyphenated (github-workflow-sync.pid) likekb-sync.pid.kbSyncclone-site found by census — including two the initial map missed: theTASK_STALENESS_CADENCE_KEYmap (fair-picker staleness normalization) and theDEFAULT_HEAVY_MAINTENANCE_TASK_NAMESSSOT (mandatory for a heavy task).MaintenanceBackpressureService.{mjs,spec}.mjs+daemon.spec.mjsalso pay down pre-existing block-alignment drift — the guard's whole-file-on-touch enforcement is the intentional boy-scout paradigm (touching a file reduces its formatting debt), so the ~80 lines there are deliberate debt-reduction, not noise. The substantive #13626 change in each is a single line (the SSOT entry / the pin-array entry / the task-def assertions).maxRuntimeMswatchdog (mirrorskbSync, which omits it); a watchdog is a follow-up only if network hangs prove an issue.Test Evidence
npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/ test/playwright/unit/ai/config.template.spec.mjs→ 520 passed. The only non-pass isDreamServiceGoldenPathtiming out, which reproduces identically on clean dev with my changes stashed — a pre-existing local-model/graph slowness (exactly the degraded state this task improves), not a regression.registry.spec.mjstest —githubWorkflowSync getDueTask gates on enables + fires on elapsed cadence: gated-off → null, cadence-elapsed → due, within-cadence → null.daemon.spec.mjs— task-def assertscommand/args(→syncGithubWorkflow.mjs) /expectedCommand.check-block-alignmenton all changed files → EXIT 0; husky pre-commit green (11 files).Post-Merge Validation
githubWorkflowSyncenters the maintenance loop, runs on theNEO_ORCHESTRATOR_GITHUB_WORKFLOW_SYNC_INTERVAL_MScadence, and the local graph reflects recent GitHub activity (tickets/PRs) so the golden path computes fresh. Observable only on the running daemon. Each clone's gitignoredconfig.mjsmust re-materialize (npm run prepare -- --migrate-config) + the orchestrator restart to pick up the two new leaves.Cycle-2 — rebased on dev + test-setup scoping (clears @neo-gpt's review-deferred red)
CI
unitred ("orchestrator task ordering/default wiring"). Root cause:githubWorkflowSyncis enabled-by-default (local) on a 30-min cadence, so in CI's freshly-materializedconfig.mjsit is due and out-ranks other lanes in the picker acrossOrchestrator.spec's polling tests. Local missed it because the gitignoredconfig.mjswas stale (nogithubWorkflowSyncMsleaf →intervalMsundefined → not due). Fix: rebased onto the advanced dev + scopedgithubWorkflowSyncdisabled-by-default in theOrchestrator.spectest setup (mirroringprimaryDevSyncEnabled), so the new heavy lane never competes in the picker unless a test opts in. Reproduced locally (10 failed with the lane forced due) → fixed (523 passed, full orchestrator dir + config spec). The other 6 CI failures were environmental (GitHub rate-limits + ChromaDB-unavailable), not this change.Refs #13624 (orchestrator-repair epic). Authored by Grace (Claude Opus 4.8, Claude Code).