| name | copilot-sdk-worker-only |
| description | Use whenever you are writing, reviewing, or refactoring any code that
touches the GitHub Copilot SDK (`@github/copilot-sdk`). Enforces the
non-negotiable architectural rule that **all** SDK execution — chat,
coach, lightweight coach, evaluation, hints, quiz, suggestions, focus
generation, README generation, anything that calls `createSession` or
`sendAndWait` — happens inside the Copilot Worker process and never
inside Next.js (Web/API). Trigger phrases: "Copilot SDK", "createSession",
"createLoggedCoachSession", "sendAndWait", "MCP", "copilot worker",
"AI generation", "focus generation", "hint generation".
|
Copilot SDK is worker-only
Web/API is forbidden from importing @github/copilot-sdk or any
module that creates a Copilot session. AI execution lives in the
worker. Always.
This is the most important architectural invariant in Flight School.
It is not a guideline. It is a contract.
Why this rule exists
The Web/API tier serves authenticated user requests. The Copilot SDK
spawns Copilot CLI subprocesses, opens MCP connections, and holds
long-lived per-user state. Mixing those concerns inside Next.js produces:
- Tenant leakage. A shared CopilotClient at module scope inevitably
gets reused across users; the SDK's per-session token is the only safe
barrier and it is easy to bypass accidentally.
- Cold-start tax on every request. SDK startup is heavyweight; the
worker amortises it across a pool. Web/API would pay the cost per
request.
- No back-pressure. The worker bounds concurrency per user and per
process; Web/API has no equivalent and would happily spawn N CLI
processes for N concurrent requests.
- Crash blast radius. An SDK process crash inside Next.js kills the
whole web server. Inside the worker it kills a single job.
- Observability holes. All AI telemetry (spans, metrics, audit) is
centralised on the worker boundary. In-process calls bypass it.
If any of those start sounding negotiable, re-read this section.
The contract
| Rule | Allowed | Forbidden |
|---|
import { CopilotClient } from '@github/copilot-sdk' | src/worker/**, src/lib/copilot/runtime/** | everywhere else |
import { createLoggedCoachSession, createLoggedLightweightCoachSession } from @/lib/copilot/server | src/worker/** | src/app/**, src/lib/** outside the worker, hooks, components, tests of non-worker code |
import { createSession, createSessionWithMetrics } from @/lib/copilot/sessions | src/lib/copilot/runtime/**, src/worker/** | everywhere else |
session.sendAndWait, session.on(...) on a real (non-mocked) session | inside a worker job executor | anywhere in Web/API |
Web/API talks to AI only via the worker dispatch primitives:
import { executeCopilotChat } from '@/lib/copilot/execution';
import { executeCopilotCoachJob } from '@/lib/copilot/execution';
If a worker dispatch primitive does not yet exist for the job you need,
build it before adding the feature — do not "temporarily" call the
SDK in-process. There are no temporary exceptions.
Where the boundary lives in the tree
src/app/** ← Web/API. Never imports the SDK.
src/lib/<feature>/** ← Feature code. Calls execute*Job helpers.
src/lib/copilot/execution/ ← Worker dispatch primitives (HTTP client +
types + worker-required error). Web/API's
ONLY entry point into AI execution.
src/lib/copilot/server.ts ← Session factories. Worker-internal.
src/lib/copilot/sessions.ts ← CopilotClient construction + session pool.
Worker-internal.
src/lib/copilot/runtime/** ← Per-user runtime pool. Worker-internal.
src/worker/** ← Worker process: job executors actually
call createSession / sendAndWait here.
The @/lib/copilot/server module is the boundary mistake magnet. Its
exports look harmless and importable; they are not. Reviewers should
treat any new import of it outside src/worker/** as a defect.
CI enforcement
scripts/check-copilot-sdk-boundary.mjs (lands with this skill)
direct-scans the import statements of every .ts/.tsx file under
src/ and fails the build if:
- Any file outside
src/worker/** or src/lib/copilot/runtime/**
references @github/copilot-sdk in an import (including type-only
import('@github/copilot-sdk').X references).
- Any file outside the worker-internal allow-list (see
WORKER_INTERNAL_PREFIXES in the script) imports the session
factories (createLoggedCoachSession,
createLoggedLightweightCoachSession, createSession,
createSessionWithMetrics, wrapSessionWithLogging,
getConversationSession, createGenericStreamingSession,
createEvaluationStreamingSession).
Runtime call patterns (session.sendAndWait, session.on(...)) are
not pattern-matched directly — they're caught transitively because
you cannot get a CopilotSession value without importing one of the
factories, which the script blocks at the import site.
The script has no name-based allowlist and no // boundary-ok:
escape hatch. If you need to expand the worker dispatch surface,
add a new primitive to src/lib/copilot/execution/ and call that.
Adding a new AI job kind
When a new feature needs Copilot SDK output:
- Add the job kind to
src/worker/jobs/types.ts.
- Implement the executor in
src/worker/jobs/executors/<kind>.ts.
This is the only place that imports createLoggedCoachSession
et al. and calls sendAndWait.
- Add a thin dispatch helper to
src/lib/copilot/execution/ that
posts the request to the worker and returns the typed result. Use
executeCopilotChat / executeCopilotChatViaWorker as the
reference shape.
- Web/API and feature code import the helper from step 3 — never the
factories from step 2.
- Update
docs/architecture-multitenant.md's data-flow diagram and
the "How This Project Uses the SDK" section of
.github/skills/copilot-sdk/SKILL.md if
the new job kind expands the public worker contract.
Red flags in a diff
Stop and reject if you see any of these in a PR:
import … from '@github/copilot-sdk' outside src/worker/** /
src/lib/copilot/runtime/**.
import { createLoggedCoachSession } (or any createLogged*Session)
in a file under src/app/** or src/lib/<feature>/**.
await session.sendAndWait( outside src/worker/jobs/executors/**.
- A new helper in
src/lib/copilot/server.ts that exports a session
factory — these belong inside the worker now, not on the shared
surface.
- A "// TODO: move this to the worker later" comment. There is no
later. Move it now or do not ship it.
- A test that mocks
createLoggedCoachSession at a non-worker call
site — the production code is doing the wrong thing if the mock is
needed there.
Self-check before requesting review
If any box is unticked, the change is not ready.