Appearance
TypeTorch migration
The full playbook is agents/AGENTS.md in the TypeTorch docs repo (https://github.com/typetorch/docs/blob/main/agents/AGENTS.md). Read it when you can; this file carries the essentials. Before/after code for every pattern: https://github.com/typetorch/docs/blob/main/getting-started/migrate.md.
Rules
- Never create, ask for, read, print or store secrets: Open Cloud keys, signing key files (
~/.config/typetorch/keys/*), pairing codes. Don't open.envor the file named byTYPETORCH_ENV_FILE. - Never act on Roblox: no
typetorchupload, deploy (not even--dry-run), promote, rollback, approve, reject, pin, deployments, branch ls, settings, access push, kernel deploy (not even--dry-run, except a kernel update the user asked for: see "Kernel updates"), kernel restore, keys, assets sync/status, test --cloud, servers, report, alerts, backend setup, update, doctor; no place publish; no remote-claude (typetorch dev), analytics server or tunnel. These are user steps. - Never push. Commit locally on
typetorch-migration. - Don't edit
node_modules/@typetorch/*, the reference../templateor any TypeTorch checkout: copy from them. - No new RemoteEvents,
loadstring,_G, disabled guards or settings changes. - Don't change player data formats, store names or keys.
- Never invent ids: the user's, or placeholder
1. - On npm:
@typetorch/framework,kernel,transformer,cli(aliastt) anddev-server(bun installin the template gets them;npx @typetorch/cliworks too); not on npm: the backend and the Studio plugin (GitHub releases). Built: kernel patch deploys (kernel deploy, or--place-file), the cloud test before prod deploys,onStopat shutdown (kernel 0.3.2), the fleet API, the optionalAnalyticsEngine,typetorch access push(CLI 0.7.3+; servers read it from kernel 0.3.8),typetorch init(CLI 0.10; a user step that sets up a new game or repairs one, never run it yourself),typetorch serve(CLI 0.11+) andtypetorch mcpfor the Studio loop, the debug macros in@typetorch/transformer/macros(transformer 0.3+), goblox (optional, CLI 0.16+) and the darklua production pack (CLI 0.17+). Planned, never promise: content packs,typetorch test --unit,/tt grant. - No CI for the game, ever (the owner's rule): never add
.github/workflows, actions or hosted CI to the game repo, never suggest them.
Procedure
- Detect. roblox-ts? (
package.jsonwithroblox-ts,tsconfig.json,.ts). Plain Luau → stop: "TypeTorch is roblox-ts only." Empty folder → fresh setup (AGENTS.md "Fresh setup path"). Find: framework (Flamework, Knit, singletons), every*.server.ts/*.client.ts, the Rojo layout, and withgit grep -nE "<pattern>" -- src:PlayerAdded|PlayerRemoving|CharacterAdded, remotes (RemoteEvent|RemoteFunction|OnServerEvent|@rbxts/net|@flamework/networking), tags (CollectionService|@flamework/components), loops and threads (while \(true\)|task\.(spawn|delay|defer)|Heartbeat),_G|shared\., data (DataStoreService|ProfileStore|ProfileService),BindToClose, UI,:Clone()of templates. - Plan. Write
MIGRATION_NOTES.md(inventory, old → new mapping, order of commits, risks: player data always, place content, third-party networking), show it, continue unless told to wait. Log every step there. - Toolchain (branch
typetorch-migration):- The reference template next to the game:
../template(clone https://github.com/typetorch/template there if missing). Every@typetorch/*package comes from npm; no other checkouts. - The template decides the files, npm the versions: copy its
package.jsondeps/devDeps/overrides (@typetorch/framework,@typetorch/kernel, devDependencies@typetorch/transformer,@typetorch/cli,@typetorch/dev-server; no Flamework) but take the@typetorch/*versions from npm (bun add <pkg>@latest,-dfor the dev ones: the template pins CLI 0.9.1, transformer 0.2.1, framework 0.5.0), and leave out itsrbxts-transform-debug;tsconfig.json(includesrc only,typeRoots@rbxts+@typetorch,types: ["types","compiler-types"],plugins: only@typetorch/transformer),default.project.json(payload: aModelwithServer/Shared/Client/include, mapping@rbxtsand only@typetorch/framework; keep the old one aslegacy.project.json),studio.project.json,rokit.toml(rojo 7.7.0-rc.1, lune 0.10.5; thenrokit add seaofvoices/darkluafor the prod pack),scripts/build-info.ts(change itsrbxts-transform-debugimport line to@typetorch/transformer/macros). Compile only viabun run buildorbunx rbxtsc(npx rbxtscruns a placeholder package in Bun projects on Windows). goblox only if asked (bun add -d goblox; it ignores every tsconfig plugin but@typetorch/transformer). - Debug macros:
$print/$warn/$error/$assert/$dbgfrom@typetorch/transformer/macros. A game onrbxts-transform-debug: drop its tsconfig plugin, rewrite the imports (also inscripts/build-info.ts), thenbun remove rbxts-transform-debug. - Scripts:
build=bun scripts/build-info.ts && rbxtsc(it must end with the compiler),watch,studio=rojo serve studio.project.json,typetorch=typetorch. Leave outpackages,postinstallandscripts/packages.ts(an optional local override for unreleased framework/kernel changes; only if the user asks). - Bun only (delete other lockfiles).
rokit install, thenbun install. .gitignore:node_modules/ out/ include/ *.tsbuildinfo src/shared/build.ts .typetorch/ .payload.gen.project.json .tsconfig.typetorch.json .env .env.*.typetorch.json:project,universeId,placeId,creator({groupId}or{userId}: the experience owner),defaultBranch: "prod",branches: {"main": "prod"},channels: {"prod": "prod"},members: {},devBadgeId: null,approval: "prod",kernel: "node_modules/@typetorch/kernel". Never copy the template's signing fields. Servers seemembers/revoked/devBadgeIdonly after the user'stypetorch access push(kernel 0.3.8+): list it whenever they change.src/server/boot.ts→startServer(kernel, { modules: [script.Parent!.FindFirstChild("services")!], build: BUILD });src/client/boot.ts→startClient(... "controllers" ...);BUILDfrom../shared/build(generated).bun run build, commit. It passes for Knit/plain projects; a Flamework project fails on its@flamework/coreimports ("only npm scopes listed in your typeRoots") until step 4 removes them: expected.
- The reference template next to the game:
- Migrate, one commit per step,
bun run buildafter each:- Delete entry scripts,
Flamework.ignite/addPaths,Knit.Start. The payload may hold only Folders and ModuleScripts. - Services/controllers →
@Service()/@Controller()from@typetorch/framework,extends Module, constructor injection withsuper(). KnitKnitInit/KnitStart→onInit/onStart;GetService→ injection. - Flamework projects are mostly a swap: same decorator and lifecycle names, imports from
@typetorch/framework(alsoModding,Reflect,t; custom decorators use@metadata typetorch:parameters injectable),extends Module+super(),@flamework/networking→createNetwork(predict→emit,invokeWithTimeoutin seconds: the codemod keeps the arguments, so grep for it and turn millisecond values like5000into seconds), components → observers.Dependency<T>()stays (from@typetorch/framework): the cycle breaker (withLazy<T>) and the way plain classes reach modules, fromonIniton; move calls in constructors, field initializers and module top level into methods. Thenbun removeevery@flamework/*andrbxts-transformer-flamework, deleteflamework.build,flamework.json,include/flamework. Type ids changed format: data saved under Flamework ids won't match (flag it). The swap-safety pass is the real work. - Swap safety: everything in
this.trove, and no cleanup function callstrove.remove/add/extend(it throws while cleaning; before framework 0.5.2 that aborts the whole generation stop; a child fromtrove.extend()is cleaned by its parent anyway);this.ctx.persist/playerStateandDependency<T>()assigned inonInitoronStart, never in field initializers or constructors; libraries with module-level setup (Vide's stepper, TopbarPlus, anything that connects or makes GUIs when required) stepped from the trove or loaded once outside the payload; looped animations and cutscenes persisted and resumed after a swap;TypeTorch.onSwapOut(runs before any cleanup) for last-resort saves; no module-level state (instance fields, orthis.ctx.persist("key.v1", () => init)with plain data only; per-player maps viathis.ctx.playerState("key.v1", init), removed on a real leave); players viaonPlayerAdded(player, playerTrove)/observePlayers, idempotent (persisted set for one-time effects), real leaves viathis.trove.connect(Players.PlayerRemoving, …); tags/characters viaobserveElementor@rbxts/observers(stop function in the trove); loops only inonStartorthis.trove.add(task.spawn(…)); no_G/shared;task.*through the trove;onStopruns at shutdown (kernel 0.3.2) but not when a server closes mid-swap, so it is for short extras, never the only place data is saved (aBindToClosethat must stay binds once per server); keeponInitshort (a new server's boot waits about 6 s). - Networking: one
src/shared/net.tswithcreateNetwork<ClientToServer, ServerToClient>(); server.on/.handle(returns[value]or[false, reason])/.fire..., client.fire/.invoke/.on; every disconnect into the trove; delete all remotes; no template literal types. - UI: code-built in the trove; Studio-built stays in StarterGui, tagged and driven by
observeElement; charm atoms per generation (persist plain values);popIn/popOut/bump; onePopupQueue. - Player data: the library lives in the place (
ServerStorage.Packages.<Lib>+ aDataHostScript, user steps), handles inpersist, load on join, release on real leave, store names split byTypeTorch.channel. Library loads, saves and releases run as jobs on DataHost's thread (library.TypeTorchJobs), or withTypeTorch.runDetachedwhen the place runs kernel 0.3.8+ (the guide's option B; DataHost then only requires the library): a deploy stops the generation's threads mid-call, which jams the library for that player. ProfileStore or ProfileService: use the Player data guide'sDataServicefor that library (keep itsTypeTorchTestline: place scripts never run in the cloud test, so waiting forDataHostthere fails every prod deploy). Developer products: the PurchaseId goes into the profile with the grant,PurchaseGrantedonly after a save holds it (grantOnce), never onlypersist. Others: wrap unchanged and flag. - The cloud test runs
onInit/onStartagainst the live game's data (real DataStores, MemoryStores, MessagingService, HTTP; no players;TypeTorch.channelisdevthere with CLI 0.7.3 or newer, so only channel-split stores point at dev data; theAnalyticsEnginesends nothing): guard global resets, raw MessagingService publishes and "server started" rows withworkspace:GetAttribute("TypeTorchTest"), and list the guards inMIGRATION_NOTES.md. Move MessagingService topics toTypeTorch.messaging.subscribe/publish(kernel 0.3.8; one kernel-held topic, no re-subscribe per swap, dev branches don't reach prod, a no-op in the cloud test); messages stay under 1 KiB. - Hot assets only if asked:
hotAsset(key, template). Analytics only if asked:new AnalyticsEngine()on server and client, in a low-loadOrdermodule'sonInit(it starts on the first construction; its backend is a user step).
- Delete entry scripts,
- Checks:
bun run build;bun run typetorch build(printsbuilt <id> ... modules);out/shared/net.luauhast.guards; the pattern scan prints nothing:git grep -nE "Players\.PlayerAdded\.Connect|new Instance\(\"(Remote|UnreliableRemote)(Event|Function)\"\)|_G\b|@flamework/|Flamework\.(ignite|addPaths)|Knit\.Start" -- src; review every hit ofgit grep -nE "trove\.add\(\(\) =>.*\.(remove|add|extend)\(|invokeWithTimeout\(" -- srcby hand; commit, rebuild: no-dirty;bun run typetorch build --branch prod;rojo build studio.project.json -o .typetorch/studio-check.rbxl. - Stop at user actions (rule 2).
- Finish with "What you need to do" (numbered, filled in): fill the ids; Game Settings (HTTP on, Studio API access on); create the Open Cloud key (
asset:read,asset:write, Luau Execution read/write (the cloud test before every prod deploy),universe-messaging-service:publish, DataStoreuniverse-datastores.objects:read+:create+:update(the shared deploy number and the signed settings:access push,backend setup), and the place key's Luau Execution scopes (universe.place.luau-execution-session:readand:write) plusasset:readforkernel deploy(universe.place:writeonly to publish a place file:--replace-place,--place-file,kernel restore <file>); nouniverse:write/universe:read, and notlegacy-asset:manage, which API keys can't get today; putOPENCLOUD_API_KEY=in the game repo's.env(gitignored);bun run typetorch doctor;bun run typetorch keys init+keys init --fallback, commit, back up the key files and the.envoffline; members intypetorch.json, thenbun run typetorch access push(again after every change); kernel into the place (new place:kernel deploy --dry-runthen--replace-place --yes; place with content:kernel deploy --dry-run --installpatches the live place with no download when its "Allow place to be updated using Save Place API" setting is on, else File > Download a Copy andkernel deploy --dry-run --install --place-file <file> --base <version>; check the summary, then the same without--dry-run; or copy the three kernel folders from.typetorch/place.rbxlin Studio and publish);ServerScriptService.LoadStringEnabledoff in a live game (on only in a test place for remote-claude'srun_luau;doctorreports it); remove the old scripts in Studio just before the first deploy; data library +DataHostin the place; test in Studio (the TypeTorch Studio plugin +bun run typetorch serve, Play); check F9[TypeTorch] kernel ...; dev branch deploy +/tt new dev+ two deploys while playing (+ a product bought during a deploy; after each deploy read Server > Status "Health window: n/3 errors": 3 errors in 30 s roll every server back, so fix them or settypetorch.json"health": { "errors": ... }above the count, kernel 0.3.7+); a game with live players: the go-live checklist (docsguides/go-live-checklist.md) before the first prod deploy; first prod deploy frommain(cloud test, y/N, signed); rollback drill; optional: the fleet API and analytics. Then post the report (template in AGENTS.md).
Kernel updates (only when the user asks)
Manual, and the one flow where you run kernel deploy. Run bun run typetorch kernel deploy --dry-run (it patches the kernel folders and the kernel's settings into the place's newest published version and saves nothing), read the summary (only the kernel folders may change), show it and ask. After the user's yes, the same command without --dry-run plus --yes saves and publishes through Luau Execution, with no download; that needs the place's "Allow place to be updated using Save Place API" setting on. Undo: bun run typetorch kernel restore --version <the version before>. If that setting is off (a Studio-made place), the user downloads a copy in Studio (File > Download a Copy) and gives you the file and its place version; then kernel deploy --dry-run --place-file <file> --base <version> and, after the user's yes, the same with --yes (kernel restore <file> undoes it). Or, with the Roblox Studio MCP and the place open, replace the three kernel folders in Studio from node_modules/@typetorch/kernel and the user publishes from Studio. Then the user joins a fresh server: F9 shows [TypeTorch] kernel <new version>. Details: AGENTS.md "Updating the kernel in a game (agents)".