Getting started
Install the pinned toolchain with tools/setup.py, sync the project into Studio with Rojo, and press Play to run the spec gate and boot both kernels.
The repository runs as cloned. The boot scripts are wired, the Game layer is in place with its default services, and
pressing Play in Studio runs the spec gate and boots a kernel on the server and on each client. Getting there takes
three commands. After that, game work happens in src/*/Game and ends with python tools/check.py passing. The
Workspace overview covers that layer in depth.
Requirements
Section titled “Requirements”- Python 3.10 or newer.
tools/setup.py,tools/check.pyandtools/scaffold.pyare Python scripts. - The Rojo plugin in Roblox Studio, to sync the filesystem project into a place.
- Access to the repository. It is private and granted individually (see the license note on the Introduction).
- aftman is optional.
tools/setup.pyuses it when it is onPATH.
Install and sync
Section titled “Install and sync”python tools/setup.py # once per machine: pinned Rojo, StyLua, Selene, luau-lsp, Luau runtime, type definitionsrojo serve ChloeKernel.project.json # then connect the Rojo plugin in Studiopython tools/check.py # after every change; must end with CHECK PASSEDtools/setup.py reads the tool pins from aftman.toml (Rojo 7.4.4, StyLua 2.0.2, Selene 0.28.0, luau-lsp 1.68.0).
With aftman on PATH it runs aftman install. Without it, it downloads the matching release zips into .tools/bin.
It also installs the standalone Luau runtime pinned in CI (0.737), downloads .tools/globalTypes.d.luau for luau-lsp,
and adds /.tools/ to .gitignore. Pins that are already installed are skipped without network access; --force
reinstalls everything. tools/check.py puts .tools/bin on its own PATH, so add that folder to your shell PATH
only if you want to run rojo directly as in the second command.
ChloeKernel.project.json maps src/Shared to ReplicatedStorage, src/Server to ServerScriptService and
src/Client to StarterPlayer.StarterPlayerScripts. Workspace, Lighting, terrain, models and UI are authored in
Studio and live in the place file, not on disk.
python tools/check.py runs exactly what CI runs, 16 steps: StyLua, Selene, the directive linter, catalog freshness,
both Rojo builds, strict luau-lsp type analysis, every Luau regression runner, the vendor tool test and the tooling
tests. --quick runs the inner-loop subset and --fix formats with StyLua first. See check.py for
each step and how to read a failure.
What boots on Play
Section titled “What boots on Play”The server script src/Server/Main.server.luau runs these steps in order:
- In Studio, the spec gate runs the shared suite (
ReplicatedStorage.ChloeKernel.Tests) and the server suite (ChloeKernelServer.Tests). A failure stops the script before the kernel boots. The play session keeps running so the Output window stays readable. Ifsrc/Server/Game/Testsexists, the game suite runs next and gates the boot the same way. ServerKernel.boot()creates the kernel, starts a session for every player who joins, and binds oneBindToClosefor the close phase.src/Server/Bootstrap.luaucallsrequire(script.Parent.Game)(kernel).Game/init.luaubuilds the channel handles fromGame.ChannelswithRegistry.server, sets them asNeton every service listed inGame/Registry.luau, and registers each service.Kernel:start()runs every serviceinit, then everystart, in dependency order, and prints the boot line.- Diagnostics for the F8 debug panel start in Studio, or in a live place whose
ReplicatedStoragehas theChloeKernelDebugattribute set to true. - In Studio, the gate runs
kernel:securityAudit()and fails on any High finding. After the first player joins (not in Run mode), it drives hostile payloads through every channel’s validators and fails if a validator throws.
The client script src/Client/Main.client.luau is shorter. In Studio it runs the client game specs in
src/Client/Game/Tests when that folder exists and stops before boot if any fail. Then it calls Kernel.boot(), runs
src/Client/Bootstrap.luau (which loads Game with Registry.client handles and registers every controller), calls
start(), and attaches the F8 debug panel, which stays off outside Studio unless ChloeKernelDebug is set.
A clean checkout of v0.9.1 with the Unreleased fixes prints one [TestKit] PASS line per suite with its test count,
spec module count and time (435 tests in the shared suite and 809 in the server suite, 1,244 in total), then the boot lines and the fuzz summary:
[TestKit] PASS: 435/435 tests across 51 specs in <ms>ms[TestKit] PASS: 809/809 tests across 56 specs in <ms>ms[ChloeKernel 0.9.1] Server booted 1 services in <ms>ms[ChloeKernel 0.9.1] Client booted 2 services in <ms>ms[Fuzz] <channels> channels, <cases> cases, validators only, 0 validator errorsThe server boots one service by default (ComponentService) and each client boots two (ComponentController and
UIController). The version in the boot line is Kernel.Version, and start() also writes it to the
ChloeKernelServer or ChloeKernelClient attribute on ReplicatedStorage for external tooling.
Where your code goes
Section titled “Where your code goes”Game code lives in three Game folders. The Bootstraps load them between kernel boot and start, and nothing else in
src/ needs to change for a game.
| Folder | Studio destination | Holds |
|---|---|---|
src/Shared/Game |
ReplicatedStorage.Game |
Channels.luau (every network channel) and Config/ (client-visible tunables). Exploiters can read all of it. |
src/Server/Game |
ServerScriptService.Game |
init.luau, Registry.luau, Services/, and Components/, Config/, Data/, Tests/ as you add them. |
src/Client/Game |
StarterPlayerScripts.Game |
init.luau, Registry.luau, Controllers/, and Screens/, Components/, Tests/ as you add them. |
Main.server.luau, Main.client.luau and the two Bootstrap.luau files own boot ordering. Leave them alone unless a
framework driver must attach before services. The client Bootstrap also attaches the framework’s simulation control
window (SimPanel); its comment says to delete those two lines for a real game. The full layout, including tools/,
docs/agents and templates/, is in Project layout.
Your first service
Section titled “Your first service”Generate files instead of writing boilerplate. scaffold.py adds the module, registers it in Registry.luau after
its dependencies, and creates a spec.
python tools/scaffold.py service Wave # src/Server/Game/Services/WaveService.luau + Registry + Tests/WaveService.spec.luaupython tools/scaffold.py controller Wave # src/Client/Game/Controllers/WaveController.luau + Registry + Tests specDeclare the channels once. Server and client both require this module, so the schemas cannot drift. A
client-to-server channel needs a RateLimit and either Validate rules or Open = true; the directive linter
(CK012) fails the check otherwise.
--!strict-- Every network channel the game uses.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Registry = require(ReplicatedStorage.ChloeKernel.Net.Registry)
return Registry.define({ Wave = { Kind = "Intent", Schema = { "NumberU8" }, RateLimit = 1, Validate = { [1] = { Range = { 1, 3 } } } }, Waved = { Kind = "State", Schema = { "String", "NumberU8" } },})The service reads its channels through self.Net, which Game/init.luau injected before registration. By the time
the handler runs, the session exists, the rate limit has passed and the Range rule has run.
--!strict-- WaveService: relays emote waves to every player.
local WaveService = { Name = "WaveService", Dependencies = {},}
function WaveService.init(self: any, kernel: any) self.Kernel = kernelend
function WaveService.start(self: any) self.Net.Wave.on(function(session: any, style: number) self.Net.Waved.broadcast(session.Player.DisplayName, style) end)end
function WaveService.stop(_self: any) end
return WaveServiceThe controller binds the action on keyboard, gamepad and touch, and owns its connections through a
scope that stop() closes.
--!strict-- WaveController: sends waves and shows the ones other players send.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local InputDriver = require(ReplicatedStorage.ChloeKernel.InputDriver)
local WaveController = { Name = "WaveController", Dependencies = {},}
function WaveController.init(self: any, kernel: any) self.Kernel = kernel self.Scope = kernel:createScope()end
function WaveController.start(self: any) local Input = self.Scope:add(InputDriver.attach(self.Kernel)) Input:bindAction("Wave", { Keyboard = Enum.KeyCode.G, Gamepad = Enum.KeyCode.DPadUp, TouchButton = true, Handler = function(state: Enum.UserInputState) if state == Enum.UserInputState.Begin then self.Net.Wave.fire(1) end end, }) self.Scope:add(self.Net.Waved.on(function(name: string, style: number) print(`{name} waved (style {style})`) end))end
function WaveController.stop(self: any) self.Scope:destroy()end
return WaveControllerRun python tools/check.py, then press Play. The game suite now prints its own [TestKit] PASS line, and the boot
lines count two services on the server and three on each client. Services and controllers
and Channels go further. Claude Code users can run the /new-game skill, which walks the same
design, plan, scaffold and check pipeline from a game idea (Agent workflow).
Starter templates
Section titled “Starter templates”templates/ holds three small complete games: a team gun arena, a wizard duel and a checkpoint obby. Each is one server module and one client module built on framework APIs only. They show kits configured and wired together with real gameplay attached.
Pinning a version
Section titled “Pinning a version”Every tagged release ships two models:
ChloeKernel-<tag>.rbxm, the full project from build.project.json (framework, specs, TestKit, Simulation, boot
scripts and the Game skeleton), and ChloeKernelRuntime-<tag>.rbxm, the framework only, built from
runtime.project.json without specs, benches, TestKit or Simulation. Insert one into a place to pin an exact version
instead of syncing main, or build them locally:
rojo build build.project.json -o ChloeKernel.rbxmrojo build runtime.project.json -o ChloeKernelRuntime.rbxmPushing a v* tag runs the CI gates, builds both models and cuts the release from the matching CHANGELOG.md
section. Releases and vendoring covers the runtime model and tools/vendor.py for
keeping a vendored copy in sync without overwriting local edits.
Next steps
Section titled “Next steps”- Read Core concepts: the kernel, services, sessions, hooks, the Bus, scopes and the close phase.
- Read
AGENTS.mdin the repository and the Workspace overview. Both apply to people as much as to agents. - Skim the Cookbook and
docs/agents/Catalog.mdso you know what already exists before writing it. - Before shipping, read Fuzz and security audit and run
kernel:securityAudit()on the finished channel set.