Context
Verified 2026-08-16 while measuring repository structure for #17238. neomjs/neo is the engine repository — the engine, its build pipeline, and the published neo.mjs package. ai/ is the Brain: swarm services, MCP servers, daemons. The dependency direction is one-way by design — the Brain may consume the engine; the engine must not consume the Brain.
Seven files under buildScripts/ violate that direction — nine crossings in total — corrected 2026-08-16, see the census note below. All six confirmed at the exact lines:
Class A — engine build/release tooling importing Brain services. These are genuinely engine-side concerns reaching across the boundary:
| file:line |
imports |
buildScripts/docs/index/labels.mjs:23 |
../../../ai/services/github-workflow/LabelService.mjs |
buildScripts/docs/rebuildContentIndexesAndSeo.mjs:10 |
../../ai/services/github-workflow/shared/reconcileActiveChunks.mjs |
buildScripts/release/publish.mjs:25 |
{GH_SyncService} from ../../ai/services.host.mjs |
Class B — agent-serving scripts misfiled in an engine directory. These are not the engine reaching into the Brain; they are Brain files sitting in buildScripts/util/, and two reach ai/graph/identityRoots.mjs — the identity graph itself, deeper than any Class A target:
| file:line |
imports |
buildScripts/util/agentCoAuthorEmails.mjs:1 |
../../ai/graph/identityRoots.mjs |
buildScripts/util/deriveFleetRoster.mjs:4 |
../../ai/graph/identityRoots.mjs |
buildScripts/util/agent-preflight.mjs:8 |
../../ai/scripts/setup/initServerConfigs.mjs |
buildScripts/devCockpit.mjs:223,234,269 |
../ai/mcp/server/shared/helpers/localBearer.mjs, ../ai/services/fleet/fleetLaunchContract.mjs — three dynamic await import(...) sites |
The two classes share this ticket's AC but not its fix: Class A moves a concern (the work belongs Brain-side), Class B moves a file (the work is already Brain-side and merely misfiled). Independent corroboration that Class B is agent-serving rather than engine code: all three are already classified agent-side by an existing structural audit of engine-directory files.
⚠️ Census correction #2, 2026-08-16 (final: 7 files / 9 crossings). The count went 3 → 6 → 7. The seventh is buildScripts/devCockpit.mjs, which crosses three times via dynamic await import(...) — invisible to any from-anchored sweep, including the directory grep that produced the six. Independently re-measured with a pattern covering from/import/require, both quote styles, and dynamic form: 9 crossings, 7 files, all under buildScripts/.
src/** is clean — zero crossings, re-verified with the same comprehensive pattern. Its one apparent hit, src/worker/App.mjs:779 → import('../ai/Client.mjs'), resolves from src/worker/ to src/ai/ — the engine loading its own Neural Link socket, not a boundary crossing. src/ai/ itself imports nothing outside src/ (0 hits), so the socket carries no Brain coupling at all.
⚠️ Census correction, 2026-08-16. This ticket originally said "three exact sites" in its title and body. It was six. The original three came from an external structural review; I verified those three at their exact lines and stopped, never running a sweep of my own over the directory — I confirmed a set someone else handed me instead of enumerating the real one. Caught by @neo-opus-ada running this ticket's own AC-1 as written ("grep over the whole directory, not only the three named files") rather than as a formality. The failure mode that AC prevents, stated plainly: with "three" in the title, a reviewer checks three, finds them fixed, and approves while buildScripts/ still imports ai/ in three places — a green that means nothing.
The Problem
This is not a hypothetical layering complaint — it is the only thing making the engine's build pipeline depend on the agent OS existing.
src/** itself is clean: a grep for relative imports from src/ into ai/ or apps/ returns zero. The engine's runtime respects the boundary completely. Only its build and release tooling crosses, and it crosses into the one tree that is furthest from it.
The practical consequence is concrete rather than aesthetic: buildScripts/release/publish.mjs is the script that performs the atomic dev → main release commit. It cannot run without ai/services.host.mjs. Releasing the engine therefore requires the agent OS to be present and importable — a coupling nobody chose and nothing documents.
The same class in the opposite direction is #17237 (Neo.util.Env publishing a Brain-internal parser as public engine API). Together they suggest the boundary is eroding from both sides while src/** stays clean.
The Architectural Reality
The honest reading of what these three scripts are: they are content-sync and release plumbing, and content sync is produced by the swarm and merely rendered by the portal. rebuildContentIndexesAndSeo.mjs derives indexes over resources/content/**, which the Data Sync pipeline writes. labels.mjs pages GitHub labels. publish.mjs reaches for GH_SyncService.
None of these is engine-build work that happens to need a helper. All three are agent-side concerns sitting in an engine directory.
That points at the fix. There are two shapes and they are not equal:
- Promote
ai/services/github-workflow/** into a shared bucket — makes the import legal by redefining the boundary. Preserves the current file layout, but it means the engine's build tooling permanently depends on a shared tier that exists only to host this.
- Move the three scripts to the Brain — makes the import legal by putting the code where its concern already lives, and lets the swarm own content sync end to end.
The second is the honest reading and the recommended shape. It also leaves the engine's build pipeline able to run with ai/ absent, which is the property that actually matters.
The Fix
Relocate the three scripts (or the concern they carry) to Brain-side ownership so that no file under buildScripts/ imports from ai/. Destination validated via structural-pre-flight at pickup — buildScripts/ai/** is the existing sibling precedent for agent-serving build tooling and is the likely home, but the choice is made against the tree, not asserted here.
The release path needs care: publish.mjs is invoked by the release workflow, so its relocation must keep the release entry point working. That is the one sequencing constraint in this ticket.
Decision Record impact
amends ADR 0004 §3.4 (updated 2026-08-21 at pickup — the original none said no ADR defines this boundary; the boundary itself has none, but ADR 0004 §3.4 RECORDS the release-cut composition this ticket changes (publish.mjs calling runFullSync in-process). The severance PR amends that section to the two-command composition; caught by @neo-gpt'''s PR #17506 round-1 RA-3.)
Acceptance Criteria
Out of Scope
- The reverse-direction violation (#17237) — same boundary, separate owner, separate fix.
- Any repository split. This ticket is worth landing whether or not any extraction ever happens; it is a precondition, not a consequence.
- Redefining the engine↔Brain contract in an ADR.
Avoided Traps
- Making the import legal by widening the boundary. A shared bucket that exists solely to legalize three imports moves the violation into the definitions rather than removing it.
- Treating this as cosmetic. The falsifiable consequence is that engine release tooling cannot run without the agent OS. That is checkable, and it is the AC.
Related
#17237 (same boundary, opposite direction) · #17238 (repository structure; this is a precondition for any extraction) · ADR 0029
Live latest-open sweep: latest 20 open issues checked 2026-08-16T19:14:37Z; no equivalent. A2A in-flight claim sweep: 30 most recent messages, newest 2026-08-16T14:10Z; no claim on this scope.
Structure-map gate: npm run --silent ai:structure-map -- --files --loc executed this session.
Origin Session ID: b17338dd-b474-494f-b08c-683044de2ddb
Retrieval Hint: "engine buildScripts import ai/services directional boundary violation publish.mjs"
Context
Verified 2026-08-16 while measuring repository structure for #17238.
neomjs/neois the engine repository — the engine, its build pipeline, and the publishedneo.mjspackage.ai/is the Brain: swarm services, MCP servers, daemons. The dependency direction is one-way by design — the Brain may consume the engine; the engine must not consume the Brain.Seven files under
buildScripts/violate that direction — nine crossings in total — corrected 2026-08-16, see the census note below. All six confirmed at the exact lines:Class A — engine build/release tooling importing Brain services. These are genuinely engine-side concerns reaching across the boundary:
buildScripts/docs/index/labels.mjs:23../../../ai/services/github-workflow/LabelService.mjsbuildScripts/docs/rebuildContentIndexesAndSeo.mjs:10../../ai/services/github-workflow/shared/reconcileActiveChunks.mjsbuildScripts/release/publish.mjs:25{GH_SyncService}from../../ai/services.host.mjsClass B — agent-serving scripts misfiled in an engine directory. These are not the engine reaching into the Brain; they are Brain files sitting in
buildScripts/util/, and two reachai/graph/identityRoots.mjs— the identity graph itself, deeper than any Class A target:buildScripts/util/agentCoAuthorEmails.mjs:1../../ai/graph/identityRoots.mjsbuildScripts/util/deriveFleetRoster.mjs:4../../ai/graph/identityRoots.mjsbuildScripts/util/agent-preflight.mjs:8../../ai/scripts/setup/initServerConfigs.mjsbuildScripts/devCockpit.mjs:223,234,269../ai/mcp/server/shared/helpers/localBearer.mjs,../ai/services/fleet/fleetLaunchContract.mjs— three dynamicawait import(...)sitesThe two classes share this ticket's AC but not its fix: Class A moves a concern (the work belongs Brain-side), Class B moves a file (the work is already Brain-side and merely misfiled). Independent corroboration that Class B is agent-serving rather than engine code: all three are already classified agent-side by an existing structural audit of engine-directory files.
The Problem
This is not a hypothetical layering complaint — it is the only thing making the engine's build pipeline depend on the agent OS existing.
src/**itself is clean: a grep for relative imports fromsrc/intoai/orapps/returns zero. The engine's runtime respects the boundary completely. Only its build and release tooling crosses, and it crosses into the one tree that is furthest from it.The practical consequence is concrete rather than aesthetic:
buildScripts/release/publish.mjsis the script that performs the atomicdev→mainrelease commit. It cannot run withoutai/services.host.mjs. Releasing the engine therefore requires the agent OS to be present and importable — a coupling nobody chose and nothing documents.The same class in the opposite direction is #17237 (
Neo.util.Envpublishing a Brain-internal parser as public engine API). Together they suggest the boundary is eroding from both sides whilesrc/**stays clean.The Architectural Reality
The honest reading of what these three scripts are: they are content-sync and release plumbing, and content sync is produced by the swarm and merely rendered by the portal.
rebuildContentIndexesAndSeo.mjsderives indexes overresources/content/**, which the Data Sync pipeline writes.labels.mjspages GitHub labels.publish.mjsreaches forGH_SyncService.None of these is engine-build work that happens to need a helper. All three are agent-side concerns sitting in an engine directory.
That points at the fix. There are two shapes and they are not equal:
ai/services/github-workflow/**into a shared bucket — makes the import legal by redefining the boundary. Preserves the current file layout, but it means the engine's build tooling permanently depends on a shared tier that exists only to host this.The second is the honest reading and the recommended shape. It also leaves the engine's build pipeline able to run with
ai/absent, which is the property that actually matters.The Fix
Relocate the three scripts (or the concern they carry) to Brain-side ownership so that no file under
buildScripts/imports fromai/. Destination validated viastructural-pre-flightat pickup —buildScripts/ai/**is the existing sibling precedent for agent-serving build tooling and is the likely home, but the choice is made against the tree, not asserted here.The release path needs care:
publish.mjsis invoked by the release workflow, so its relocation must keep the release entry point working. That is the one sequencing constraint in this ticket.Decision Record impact
amends ADR 0004 §3.4(updated 2026-08-21 at pickup — the originalnonesaid no ADR defines this boundary; the boundary itself has none, but ADR 0004 §3.4 RECORDS the release-cut composition this ticket changes (publish.mjs calling runFullSync in-process). The severance PR amends that section to the two-command composition; caught by @neo-gpt'''s PR #17506 round-1 RA-3.)Acceptance Criteria
buildScripts/imports fromai/— verified by grep over the whole directory, not only the six named files. This AC is the one that found the undercount; run it as a sweep, never as a spot-check of the table above.buildScripts/release/publish.mjs's replacement is reachable from the release workflow, and the workflow is updated in the same PR if its path changed.ai/unavailable — the property this ticket exists to restore. A mutation control: the check fails if the import is reintroduced.Out of Scope
Avoided Traps
Related
#17237 (same boundary, opposite direction) · #17238 (repository structure; this is a precondition for any extraction) · ADR 0029
Live latest-open sweep: latest 20 open issues checked 2026-08-16T19:14:37Z; no equivalent. A2A in-flight claim sweep: 30 most recent messages, newest 2026-08-16T14:10Z; no claim on this scope. Structure-map gate:
npm run --silent ai:structure-map -- --files --locexecuted this session.Origin Session ID: b17338dd-b474-494f-b08c-683044de2ddb Retrieval Hint: "engine buildScripts import ai/services directional boundary violation publish.mjs"