Working with agents
Why a Rogen layout suits coding agents, and how to tell an agent about Rogen.
Why
A coding agent works best when the context it needs is in one place, and when you can check what it did. A Rogen layout gives it both.
- One folder, one feature. Everything about the shop is in
Shop/. Point the agent at that folder and it has the right context, without reading three trees to find the pieces. - A small blast radius. An agent's change shows up as files in the folders it touched. A diff confined to
Shop/touched the shop and nothing else, and when features don't require each other, it can only break the shop. You can see that from the file list before reading a line. - Boundaries a linter can check. "Features don't require each other." "
platformdoesn't requirefeatures." "Sharedrequires onlyShared." Each is a rule about paths, and feature folders put the layer, the feature and the side in every path. State the rules inAGENTS.mdand enforce them with a linter: the agent runs it, sees the boundary it crossed, and fixes it before you review. Grouping byControllers/andServices/can't express these rules at all. See Enforcing the Rules. - Placement is declared. Where a file ends up in Studio is decided by a short
routesmap in the config, not by conventions the agent has to guess from the tree. - No generated file to break. The
*.project.jsonis output. Rogen replaces it on every build, so there's no hand-maintained project file for an agent to corrupt. - It checks its own work.
rogen whereprints where a file lands and why, before or after it exists.rogen buildprints diagnostics for files that match no route, names that look like a route but aren't, and instances that clash. The agent runs both after moving files and fixes what they report.
An agent that doesn't know Rogen reads the disk path as the instance path. It edits the generated *.project.json, requires a server module from shared code because the two sit side by side on disk, or names a file HttpClient.luau without noticing that the Client suffix sends it to the client. A short set of rules prevents all three: route every file with a folder, build once after a batch of changes, and require by instance path. Install it as a skill, or paste it into your AGENTS.md.
Install the skill
The skill lives at skills/rogen in the Rogen repository, in the open Agent Skills format. Claude Code, Codex, Cursor, Gemini CLI, Copilot and most other coding agents read it. Install it with the skills CLI, which asks which agents to install it for:
npx skills add LDGerrits/rogenThe skill keeps its tag, config and CLI reference in a second file that the agent reads only when it needs it.
Or add it to AGENTS.md
## Rogen
Rogen reads the folder and file names under each config's `rootDirs` and writes the Rojo project file. Folders decide where code runs, so the disk path is not the instance path.
### Placing files
Route with folders. Give each feature one folder per side, named after the config's route keys, and put every file inside one of them:
```
src/Inventory/Server/InventoryService.luau -> ServerScriptService/Inventory/InventoryService
src/Inventory/Client/InventoryController.luau -> StarterPlayer/StarterPlayerScripts/Inventory/InventoryController
src/Inventory/Shared/InventoryTypes.luau -> ReplicatedStorage/Shared/Inventory/InventoryTypes
```
- Read the keys and their targets once from the config's `routes`, since a repo can rename or add them; `rogen list --json` resolves an `extends` chain.
- A file's instance path is its route's target, then its folders without the routing folder, then its name as Rojo reads it (`Save.server.luau` is `Save`).
- Inside a routing folder, a route key at the end of a name is ignored: `Shared/HttpClient.luau` stays `HttpClient`.
- `rogen where <path>` prints where a file lands and why, before or after it exists. Given an instance as Studio prints it (`rogen where ServerScriptService.Inventory.Save:12`), it prints the file behind it.
### After a batch of changes
- Run `rogen where` on the paths you added, moved or renamed, and check each lands where you meant. It also shows files left out without a warning: pruned by a dormant tag, replaced by another file, excluded by a glob, or displaced by a template node.
- Then run `rogen build --all` once and fix every warning that names your files.
- Leave `rogen watch` and `rojo serve` to the user: they never exit, and `rogen build` is safe beside them.
### Requires and imports
- `Shared/` code runs on both sides, so it can't require anything in `Server/`: the client can't see `ServerScriptService`.
- Luau: require by instance path (`ReplicatedStorage.Shared.Inventory.InventoryTypes`); string requires resolve against the instance tree, so `./` only reaches files routed into the same folder.
- roblox-ts: import by relative file path; `rbxtsc` resolves it through the project file, so run `rogen build` before compiling.
- Keep the import boundaries the repo states, in `AGENTS.md` or its lint config. A crossed boundary means the code is in the wrong place: move it, don't add an exception.
### Ownership
- `rogen build` overwrites the `*.project.json` each config writes: put changes in the config, or in the `template` it merges into.
- The `syncDir` (`out`, `dist`) is compiler output: edit the sources in the root directories.
- Add a place with `rogen init <name> --json` rather than by hand: it writes the place's config and the extra files Darklua or roblox-ts need for it. Make every edit in its `nextSteps.setup`, such as roblox-ts's `include`.
For tags and variants (`Foo.mock.luau`), marker files, suffixes, `.meta.json`, extra configs and CLI flags, read https://rogen-playfully.vercel.app/docs/v2.The snippet covers the mistakes an agent makes on its own. The rest it can look up when a build prints a diagnostic, since every diagnostic names what to change.
A tool that drives Rogen can read --json from build, where, list and init instead of parsing text: see JSON output.