Context
Filed 2026-05-08 as M6 sub-issue 2 (github-workflow) — second per-server SDK migration in the M6 epic #10986 sequence (KB → GH-WF → NL → MC). Sequenced after KB (#10991) per parent epic Out-of-Scope rationale: each migration touches ai/services.mjs import paths, so concurrent PRs would merge-conflict at the SDK aggregator. Linear sequencing keeps the file under single-edit lock.
Per parent M6 epic #10986 §The Fix and learn/agentos/v13-path.md §3 D4: services move from ai/mcp/server/github-workflow/services/*.mjs → ai/services/github-workflow/*.mjs. ai/services.mjs becomes a re-export aggregator with stable <Server>_<ServiceName> alias contracts (consumer-side unchanged). M2 BaseServer substrate (#10965) provides the extension contract; per-server Server.mjs getDependentServices() override updated to import from new SDK paths.
The Problem
GH-WF services currently live under ai/mcp/server/github-workflow/services/ — 10 service files + 2 helper subdirectories (queries/, sync/) + toolService.mjs (MCP tool router). 7 of the 10 services are exposed via ai/services.mjs's aliasing layer (GH_HealthService, GH_IssueService, GH_LabelService, GH_LocalFileService, GH_PullRequestService, GH_RepositoryService, GH_SyncService). 3 services (AgentStateService, DiscussionService, GraphqlService) are internal — not currently SDK-exported.
The friction (per parent epic #10986 framing): the on-disk shape conflates "service implementation" with "MCP transport endpoint" — services are owned by the server-specific dir rather than by the SDK boundary. This sub-issue normalizes GH-WF to the existing ai/services/ConceptService.mjs flat-shape precedent (and matches the KB migration shape from #10991).
The Architectural Reality
Current state (verified 2026-05-08 via ls ai/mcp/server/github-workflow/services/ + grep on ai/services.mjs):
| File / dir |
SDK alias (ai/services.mjs) |
SDK-exported |
AgentStateService.mjs |
(not exported — internal) |
— |
DiscussionService.mjs |
(not exported — internal) |
— |
GraphqlService.mjs |
(not exported — internal helper) |
— |
HealthService.mjs |
GH_HealthService |
✓ |
IssueService.mjs |
GH_IssueService |
✓ |
LabelService.mjs |
GH_LabelService |
✓ |
LocalFileService.mjs |
GH_LocalFileService |
✓ |
PullRequestService.mjs |
GH_PullRequestService |
✓ |
RepositoryService.mjs |
GH_RepositoryService |
✓ |
SyncService.mjs |
GH_SyncService |
✓ |
toolService.mjs |
(not exported — MCP tool router) |
— |
queries/ (subdir) |
n/a |
helpers |
sync/ (subdir) |
n/a |
helpers |
M2 BaseServer substrate (#10965): GH-WF Server.mjs reduced 226 → 110 LOC during M2 migration (#10974); getDependentServices() override resolves [SyncService] (startup sync against GitHub). Migration updates this override's import paths.
makeSafe() Zod-wrapping (ai/services.mjs:201-207): wraps all 7 SDK-exported services. Migration preserves wrapping; only import paths change above the wrap.
Subdirectories: queries/ and sync/ contain helper modules consumed by their parent services (most likely GraphqlService and SyncService respectively). They migrate alongside their parent services to preserve internal-import resolution.
Existing flat-shape precedent: ai/services/ConceptService.mjs (Concept Ontology) demonstrates the target shape; KB sub-issue #10991 is the just-filed sibling that establishes the per-server pattern.
The Fix
Per-file moves (10 service files + 2 helper subdirs + 1 tool router = 13 entities):
| Source path |
Destination path |
ai/mcp/server/github-workflow/services/AgentStateService.mjs |
ai/services/github-workflow/AgentStateService.mjs |
ai/mcp/server/github-workflow/services/DiscussionService.mjs |
ai/services/github-workflow/DiscussionService.mjs |
ai/mcp/server/github-workflow/services/GraphqlService.mjs |
ai/services/github-workflow/GraphqlService.mjs |
ai/mcp/server/github-workflow/services/HealthService.mjs |
ai/services/github-workflow/HealthService.mjs |
ai/mcp/server/github-workflow/services/IssueService.mjs |
ai/services/github-workflow/IssueService.mjs |
ai/mcp/server/github-workflow/services/LabelService.mjs |
ai/services/github-workflow/LabelService.mjs |
ai/mcp/server/github-workflow/services/LocalFileService.mjs |
ai/services/github-workflow/LocalFileService.mjs |
ai/mcp/server/github-workflow/services/PullRequestService.mjs |
ai/services/github-workflow/PullRequestService.mjs |
ai/mcp/server/github-workflow/services/RepositoryService.mjs |
ai/services/github-workflow/RepositoryService.mjs |
ai/mcp/server/github-workflow/services/SyncService.mjs |
ai/services/github-workflow/SyncService.mjs |
ai/mcp/server/github-workflow/services/queries/ (whole subdir) |
ai/services/github-workflow/queries/ |
ai/mcp/server/github-workflow/services/sync/ (whole subdir) |
ai/services/github-workflow/sync/ |
ai/mcp/server/github-workflow/services/toolService.mjs |
ai/services/github-workflow/toolService.mjs |
Then:
- Update
ai/services.mjs import paths (lines 17-23) — alias names (GH_*) preserved; only import paths change.
- Update
ai/mcp/server/github-workflow/Server.mjs getDependentServices() override (returns [SyncService] per #10974) — import path updated.
- Update GH-WF
config.mjs / openapi.yaml if they reference service paths.
- Update internal cross-references: services that import from
queries/ or sync/ subdirs (most likely GraphqlService and SyncService) — relative imports continue working because the entire subdir tree moves together.
- Update
toolService.mjs references if it imports from the same dir tree (relative imports preserved).
- Remove the now-empty
ai/mcp/server/github-workflow/services/ directory.
- Verify operator scripts work unchanged via the preserved
GH_* aliases.
Class names UNCHANGED. Service file names UNCHANGED. Only physical locations change.
Neo class-system compatibility note: Neo enforces filepath-to-className matching at setupClass time. GH-WF services likely follow factory-singleton pattern (similar to KB analysis in #10991) — verify during implementation. If any GH-WF service uses Neo.setupClass() with a className matching the old path, the className must be updated alongside the path move.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
ai/services/github-workflow/<ServiceName>.mjs paths |
This sub-issue + parent epic #10986 |
Flat SDK location for GH-WF services + helper subdirs |
None — physical move |
Brief note in learn/agentos/ post-M6-epic |
ls ai/services/github-workflow/ confirms move; old ai/mcp/server/github-workflow/services/ removed |
ai/services.mjs GH_* re-export aliases |
This sub-issue |
Continue exposing all 7 currently-aliased services; only import paths change |
Aliases preserved → consumer code unchanged |
JSDoc on ai/services.mjs |
Operator scripts + tests run unchanged post-migration |
GH-WF Server.getDependentServices() override |
M2 BaseServer extension contract (#10965) |
Returns [SyncService] from new SDK path |
Migration touches imports only; method signature unchanged |
GH-WF Server.mjs JSDoc |
Per-server unit specs pass |
| GH-WF unit + integration coverage |
Existing test substrate |
Continue working post-migration with no AC delta |
Test fixture imports updated alongside service moves |
n/a |
All 39 existing GH-WF unit specs pass (per #10974 baseline) + integration row green |
queries/ + sync/ subdir helpers |
This sub-issue |
Migrate as-is (whole subdir tree); preserve internal-import resolution |
If subdir-internal imports break, halt + investigate |
n/a |
Internal services (GraphqlService, SyncService) work unchanged |
Acceptance Criteria
Out of Scope
makeSafe() Zod schema reshape — wrapping pattern preserved as-is.
- Renaming GH-WF services or class names — physical move only.
- Touching
ai/mcp/server/shared/services/* — shared cross-cutting services unaffected.
- Other Tier-1 servers — knowledge-base (#10991 — already filed), neural-link, memory-core migrations are separate sub-issues filed per parent epic #10986 sequencing.
<Server>_<ServiceName> namespace prefix collapse — preserved through M6 per parent epic Out-of-Scope.
- Promoting
AgentStateService / DiscussionService / GraphqlService to SDK-exported — they're internal for a reason (state machines + GraphQL helpers). External exposure should be a deliberate decision, not a side-effect of physical move.
- Reshaping
queries/ / sync/ subdir architecture — physical move only. Whether helpers belong in their own dirs or inline with services is a future-ticket question.
ai/services.mjs re-organization — this sub-issue updates GH-WF import paths only.
Avoided Traps / Gold Standards Rejected
- Rejected: only move SDK-exported services, leave internal services + subdirs in old location. Splits GH-WF services across two top-level dirs categorized by export-status; creates ongoing cognitive cost. Move all 10 services + subdirs + toolService.mjs together to keep the directory unified.
- Rejected: flatten
queries/ / sync/ subdirs into the parent during migration. Architectural reshape, not migration. Subdirs migrate as-is; reshape is separate ticket if desired.
- Rejected: rename
GH_* aliases to drop prefix during migration. Breaks every operator script + daemon + test consumer. Reshape is future-ticket scope.
- Rejected: refactor
makeSafe Zod wrapping pattern alongside the move. Increases PR risk; breaks "do one thing well per PR".
- Rejected: collapse
GraphqlService into individual services. Architectural reshape, not migration.
- Rejected: parallel cadence with sibling M6 sub-issues (KB / NL / MC). Per parent epic #10986 Avoided Traps: each migration touches
ai/services.mjs import paths — concurrent PRs would merge-conflict. Linear sequencing keeps the SDK aggregator under single-edit lock.
- Rejected: gradual migration via indirection layer. A re-export shim under the old paths adds complexity; clean cut-over is simpler for a 13-entity scope.
Related
- Parent epic: #10986 — Migrate Tier-1 MCP services to flat SDK boundary (M6)
- Sibling sub-issue (predecessor): #10991 — Migrate knowledge-base services to flat SDK boundary (M6 sub-1; this PR's pattern precedent)
- Sibling sub-issues (successors, to be filed): neural-link, memory-core
- Predecessor milestone (cleared M6's gate): #10965 — M2 BaseServer common base class (CLOSED)
- Sibling SDK work: #10103 — SDK-layer config-file migration (config-file dual)
- Strategic anchor:
learn/agentos/v13-path.md §3 D4 + §4 M6
- M2 per-server migration precedent: #10974 — github-workflow migration to BaseServer (the analogous earlier per-server migration; this M6 sub-issue follows the same single-PR per-server pattern). Same
Server.mjs that received M2 BaseServer extension hooks now receives M6's getDependentServices() import-path update.
Origin Session ID: 33ed57b5-10ed-491d-8b8f-ce5f1223ec38
Retrieval Hint: query_raw_memories(query="M6 SDK migration github-workflow services flat shape ai/services/github-workflow GH_ aliases")
Context
Filed 2026-05-08 as M6 sub-issue 2 (github-workflow) — second per-server SDK migration in the M6 epic #10986 sequence (KB → GH-WF → NL → MC). Sequenced after KB (#10991) per parent epic Out-of-Scope rationale: each migration touches
ai/services.mjsimport paths, so concurrent PRs would merge-conflict at the SDK aggregator. Linear sequencing keeps the file under single-edit lock.Per parent M6 epic #10986 §The Fix and
learn/agentos/v13-path.md§3 D4: services move fromai/mcp/server/github-workflow/services/*.mjs→ai/services/github-workflow/*.mjs.ai/services.mjsbecomes a re-export aggregator with stable<Server>_<ServiceName>alias contracts (consumer-side unchanged). M2 BaseServer substrate (#10965) provides the extension contract; per-serverServer.mjsgetDependentServices()override updated to import from new SDK paths.The Problem
GH-WF services currently live under
ai/mcp/server/github-workflow/services/— 10 service files + 2 helper subdirectories (queries/,sync/) +toolService.mjs(MCP tool router). 7 of the 10 services are exposed viaai/services.mjs's aliasing layer (GH_HealthService,GH_IssueService,GH_LabelService,GH_LocalFileService,GH_PullRequestService,GH_RepositoryService,GH_SyncService). 3 services (AgentStateService,DiscussionService,GraphqlService) are internal — not currently SDK-exported.The friction (per parent epic #10986 framing): the on-disk shape conflates "service implementation" with "MCP transport endpoint" — services are owned by the server-specific dir rather than by the SDK boundary. This sub-issue normalizes GH-WF to the existing
ai/services/ConceptService.mjsflat-shape precedent (and matches the KB migration shape from #10991).The Architectural Reality
Current state (verified 2026-05-08 via
ls ai/mcp/server/github-workflow/services/+greponai/services.mjs):AgentStateService.mjsDiscussionService.mjsGraphqlService.mjsHealthService.mjsGH_HealthServiceIssueService.mjsGH_IssueServiceLabelService.mjsGH_LabelServiceLocalFileService.mjsGH_LocalFileServicePullRequestService.mjsGH_PullRequestServiceRepositoryService.mjsGH_RepositoryServiceSyncService.mjsGH_SyncServicetoolService.mjsqueries/(subdir)sync/(subdir)M2 BaseServer substrate (#10965): GH-WF Server.mjs reduced 226 → 110 LOC during M2 migration (#10974);
getDependentServices()override resolves[SyncService](startup sync against GitHub). Migration updates this override's import paths.makeSafe()Zod-wrapping (ai/services.mjs:201-207): wraps all 7 SDK-exported services. Migration preserves wrapping; only import paths change above the wrap.Subdirectories:
queries/andsync/contain helper modules consumed by their parent services (most likelyGraphqlServiceandSyncServicerespectively). They migrate alongside their parent services to preserve internal-import resolution.Existing flat-shape precedent:
ai/services/ConceptService.mjs(Concept Ontology) demonstrates the target shape; KB sub-issue #10991 is the just-filed sibling that establishes the per-server pattern.The Fix
Per-file moves (10 service files + 2 helper subdirs + 1 tool router = 13 entities):
ai/mcp/server/github-workflow/services/AgentStateService.mjsai/services/github-workflow/AgentStateService.mjsai/mcp/server/github-workflow/services/DiscussionService.mjsai/services/github-workflow/DiscussionService.mjsai/mcp/server/github-workflow/services/GraphqlService.mjsai/services/github-workflow/GraphqlService.mjsai/mcp/server/github-workflow/services/HealthService.mjsai/services/github-workflow/HealthService.mjsai/mcp/server/github-workflow/services/IssueService.mjsai/services/github-workflow/IssueService.mjsai/mcp/server/github-workflow/services/LabelService.mjsai/services/github-workflow/LabelService.mjsai/mcp/server/github-workflow/services/LocalFileService.mjsai/services/github-workflow/LocalFileService.mjsai/mcp/server/github-workflow/services/PullRequestService.mjsai/services/github-workflow/PullRequestService.mjsai/mcp/server/github-workflow/services/RepositoryService.mjsai/services/github-workflow/RepositoryService.mjsai/mcp/server/github-workflow/services/SyncService.mjsai/services/github-workflow/SyncService.mjsai/mcp/server/github-workflow/services/queries/(whole subdir)ai/services/github-workflow/queries/ai/mcp/server/github-workflow/services/sync/(whole subdir)ai/services/github-workflow/sync/ai/mcp/server/github-workflow/services/toolService.mjsai/services/github-workflow/toolService.mjsThen:
ai/services.mjsimport paths (lines 17-23) — alias names (GH_*) preserved; only import paths change.ai/mcp/server/github-workflow/Server.mjsgetDependentServices()override (returns[SyncService]per #10974) — import path updated.config.mjs/openapi.yamlif they reference service paths.queries/orsync/subdirs (most likelyGraphqlServiceandSyncService) — relative imports continue working because the entire subdir tree moves together.toolService.mjsreferences if it imports from the same dir tree (relative imports preserved).ai/mcp/server/github-workflow/services/directory.GH_*aliases.Class names UNCHANGED. Service file names UNCHANGED. Only physical locations change.
Neo class-system compatibility note: Neo enforces filepath-to-className matching at
setupClasstime. GH-WF services likely follow factory-singleton pattern (similar to KB analysis in #10991) — verify during implementation. If any GH-WF service usesNeo.setupClass()with a className matching the old path, the className must be updated alongside the path move.Contract Ledger Matrix
ai/services/github-workflow/<ServiceName>.mjspathslearn/agentos/post-M6-epicls ai/services/github-workflow/confirms move; oldai/mcp/server/github-workflow/services/removedai/services.mjsGH_*re-export aliasesai/services.mjsGH-WF Server.getDependentServices()override[SyncService]from new SDK pathServer.mjsJSDocqueries/+sync/subdir helpersAcceptance Criteria
queries/subdir +sync/subdir +toolService.mjsmoved fromai/mcp/server/github-workflow/services/toai/services/github-workflow/. Old directory removed.ai/services.mjscontinues exporting all 7 currently-aliased GH-WF services (GH_HealthService,GH_IssueService,GH_LabelService,GH_LocalFileService,GH_PullRequestService,GH_RepositoryService,GH_SyncService); only import paths change.ai/mcp/server/github-workflow/Server.mjsgetDependentServices()override updated to importSyncServicefrom new SDK path.queries//sync/subdirs work post-migration (relative imports preserved by moving subdirs together).Neo.setupClass()with a className matching the old path, className updated to the new path (likely N/A — GH-WF services appear factory-singleton-shape — verify during implementation).Out of Scope
makeSafe()Zod schema reshape — wrapping pattern preserved as-is.ai/mcp/server/shared/services/*— shared cross-cutting services unaffected.<Server>_<ServiceName>namespace prefix collapse — preserved through M6 per parent epic Out-of-Scope.AgentStateService/DiscussionService/GraphqlServiceto SDK-exported — they're internal for a reason (state machines + GraphQL helpers). External exposure should be a deliberate decision, not a side-effect of physical move.queries//sync/subdir architecture — physical move only. Whether helpers belong in their own dirs or inline with services is a future-ticket question.ai/services.mjsre-organization — this sub-issue updates GH-WF import paths only.Avoided Traps / Gold Standards Rejected
queries//sync/subdirs into the parent during migration. Architectural reshape, not migration. Subdirs migrate as-is; reshape is separate ticket if desired.GH_*aliases to drop prefix during migration. Breaks every operator script + daemon + test consumer. Reshape is future-ticket scope.makeSafeZod wrapping pattern alongside the move. Increases PR risk; breaks "do one thing well per PR".GraphqlServiceinto individual services. Architectural reshape, not migration.ai/services.mjsimport paths — concurrent PRs would merge-conflict. Linear sequencing keeps the SDK aggregator under single-edit lock.Related
learn/agentos/v13-path.md§3 D4 + §4 M6Server.mjsthat received M2 BaseServer extension hooks now receives M6'sgetDependentServices()import-path update.Origin Session ID:
33ed57b5-10ed-491d-8b8f-ce5f1223ec38Retrieval Hint:
query_raw_memories(query="M6 SDK migration github-workflow services flat shape ai/services/github-workflow GH_ aliases")