An Effect stack so pure (aspirational) Kit Langton will blush.
Build an Effect app and its cloud as one typed program. Joel's goal is the best Effect + Alchemy application we can build; VISION.md explains why. AGENTS.md sets exact pins, checks, and hooks. The public tree lets you steal ideas. It carries no product support promise.
The pnpm + Turborepo workspace includes a working Effect v4 CLI, tests, formatting, and type-aware linting. Vendored source mirrors cover Effect, effect-solutions, XState, and Alchemy.
Define a Contract with Effect input, output, and failure schemas plus annotations. Bind a server-side handler, then project the capability onto every agent surface. The same inspectFile capability is the stats command, POST /inspectFile with an OpenAPI document, and an MCP tool over stdio.
gh repo create <name> --template joelhooks/rat-stackClone the new repository, then install and verify it:
nvm use
pnpm install
pnpm vendor:agent-sources
pnpm turbo run check test buildNode 24.18.0 and pnpm 11.3.0 are required. The requirement is declared in .nvmrc, engines, devEngines, and packageManager.
apps/cli:- Package:
@rat-stack/cli - Role: Composition root:
stats,openapi,serve,mcp
- Package:
packages/capability:- Package:
@rat-stack/capability - Role:
defineContract,implement, and thetoCommand,toHttpApi,toToolkit,toRpc, andtoCodeModeprojections
- Package:
packages/core:- Package:
@rat-stack/core - Role: Domain example: the
inspectFilecontract, handler, and lifecycle machine
- Package:
packages/flags:- Package:
@rat-stack/flags - Role: Typed flags, Config, memory and KV adapters, stable rollouts, audited writes, and removal dates
- Package:
packages/events:- Package:
@rat-stack/events - Role: Request analytics: capture, visitor privacy rules, and Basin resources behind core's
EventSinkport
- Package:
packages/crash:- Package:
@rat-stack/crash - Role: Private crash-trace archive for the Mischief Worker
- Package:
packages/lore:- Package:
@rat-stack/lore - Role: Public content graph and traversal service
- Package:
packages/subscriber-delivery:- Package:
@rat-stack/subscriber-delivery - Role: External subscriber intake and confirmation adapter behind core's job-shaped ports
- Package:
apps/infra:- Package:
@rat-stack/infra - Role: Alchemy Stack (Cloudflare by default)
- Package:
apps/site:- Package:
@rat-stack/site - Role: Cloudflare Worker for the public site, agent discovery, and sandboxed execute
- Package:
.agent_sources/:- Package: —
- Role: Shallow upstream mirrors (gitignored clones; see README there)
The included stats command counts bytes, Unicode characters, words, and lines in a file:
pnpm cli stats README.md
pnpm cli stats README.md --jsonAfter pnpm build, you can run the built entrypoint directly:
node apps/cli/dist/cli.js stats README.mdThe same capability on the other surfaces:
pnpm cli openapi # OpenAPI 3.1 document for the REST projection
pnpm cli serve --port 3000 # POST /inspectFile, GET /openapi.json, GET /docs
pnpm cli mcp # MCP server over stdio, one tool per capability
pnpm cli mcp --code-mode # MCP server with two tools: search and execute
pnpm cli catalog --types # the `tools` declarations a code-mode program seesCode mode is the fourth projection. The model gets search for ranked matches with TypeScript signatures and execute for JavaScript with tools in scope. The program runs in a fresh Node subprocess under --permission. It cannot touch the file system or spawn processes. To call the host, it uses tools.<name>(input). The host validates the input schema and runs the same handler used by every other surface. Node's permission model allows network egress. Put a Worker or Deno runtime behind the same Sandbox service to isolate the network too.
packages/capability/src is where a capability becomes a Command, an HttpApiEndpoint, a Tool, and a catalog entry. packages/core/src/inspect-file.ts supplies the CLI's example capability. Hosted content has its own registry in apps/site/src/capabilities/index.ts. Provider adapters stay outside core (rat-stack-boundaries/no-core-adapters); apps provide them to core's job-shaped ports at composition. Add another to capabilities, then verify every projection you keep; CLI command registration lives in apps/cli/src/command.ts.
- pnpm workspaces + Turborepo
2.11.9: cachedtypecheck/test/buildacross packages from day one. - Effect
4.0.6: typed runtime, errors, filesystem service, and the three surfaces the capabilities project onto:effect/cli,effect/http-api(withOpenApi.fromApi), andeffect/ai(Toolkit+McpServer). @effect/platform-node4.0.6: Node-backed services; keep adapter and core pins matched.- TypeScript
7.0.2: strict module and index-access checks. @yielded/effect-tsrs0.2.2: runs TypeScript and Effect diagnostics intypecheckandcheck.--type-checkincludes ordinary TypeScript diagnostics;--strictmakes Effect warnings fail. It bundles tsrs8f135f106a63, contained in plain tsrs0.20.0.- tsrs
0.20.0-ts7.1.0-beta: experimental compiler targeting TypeScript7.1.0-betafortypecheck:fast. It still has no Effect diagnostics; usetypecheckorcheckfor those. - XState
6.0.0-alpha.70: real lifecycle states; do not replace those with boolean soup. @xstate/effect1.0.0-alpha.9: the official XState v6 to Effect 4 bridge:createEffectActorruns a machine as a scoped Effect,createEffectLogicmakes Effects into actors with typed failures and requirements.packages/core/src/inspect-machine.tsis the example.- Alchemy
2.0.0-beta.84: Infrastructure as Effects:apps/infra/alchemy.run.tsis the Stack; cloud providers come from@alchemy.run/cloudflare,@alchemy.run/drizzle, and@alchemy.run/sql;pnpm infra:plan/infra:deploy/infra:destroy; auth viapnpm alchemy profile edit. - Foldkit
0.169.0+@foldkit/vite-plugin0.28.0: the web UI. Server rendering and prerendering are on by default. Onevite buildwrites the browser bundle, a serverfetchfunction, and an HTML file for every public page.Cloudflare.Website.Foldkitserves the files and sends every other request to that function. The browser hydrates the same view..brain/areas/web.svxis the system page. - Oxlint + Ultracite + Oxfmt: native lint and format.
- Vitest
5.0.5+@effect/vitest4.0.6:it.effectandit.layerfor every Effect test; running Effects by hand in a test file is a lint error. @effect/tsgo0.55.0: patches TypeScript 7 inpreparefortypecheck:tsc. Effect warnings fail becauseignoreEffectWarningsInTscExitCodestays false. Patchedtscruns at the switch and on checker bumps through parity, not on every CI run. Report any checker disagreement.- varlock
1.23.0:.env.schemadeclares every variable with@env-specdecorators;pnpm checkrunsvarlock load, secrets stay in gitignored.env.local. - Vendored agent sources: Effect, effect-solutions, XState, Alchemy, Better Auth, and Foldkit via
./scripts/vendor-agent-sources.sh, each at the ref its workspace pin names (not x-algorithm; that stays app-specific). - Agent fence: lefthook pre-commit + Pi/Cursor/Claude hooks that block
git … --no-verify. Cheating should be uncomfortable and obvious.
Every dependency is pinned exactly. Review pin upgrades as stack changes.
pnpm typecheck: Check TypeScript and Effect diagnostics with strict effect-tsrspnpm typecheck:tsc: Run patched TypeScript7.0.2/@effect/tsgo0.55.0; runs at checker bumps, not in CIpnpm typecheck:fast: Fast feedback with tsrs; no Effect diagnostics;pnpm checkstays the gatepnpm typecheck:parity: Compare effect-tsrs and patchedtscerrors and exit verdicts, includingTS377xxx; also compare plain tsrs with Effect-only codes excludedpnpm bump:checkers: Bump both exact checker pins to npm latest, report bundled revision alignment, and run both parity pairspnpm check: Typecheck, check formatting, and run type-aware lintingpnpm fix: Apply Oxfmt and safe Oxlint fixespnpm test: Run the Vitest suite oncepnpm build: Compile package outputspnpm cli -- ...: Build and run the CLIpnpm vendor:agent-sources: Clone core lib mirrorspnpm turbo run check test build: Cached verification pipeline
Every clone includes AGENTS.md for repo law and commands, CLAUDE.md as Claude Code's pointer to that law, and VISION.md for project intent. Source mirrors live under .agent_sources/ after you run the vendor script. Repo-local Pi extensions belong in .pi/extensions/.
Start with the template:
gh repo create <name> --template joelhooks/rat-stackClone the new repository, then install it:
pnpm install
pnpm sources:fetchpnpm sources:fetch imports missing pinned source commits from the configured repository remotes once, including in template children. Generation reads only local Git objects and never fetches. Re-run the command when adding a new pin that is not present locally.
Rename the workspace package names before you build on the example. The current name fields are:
package.json:rat-stackapps/cli/package.json:@rat-stack/cliapps/infra/package.json:@rat-stack/infraapps/site/package.json:@rat-stack/sitepackages/capability/package.json:@rat-stack/capabilitypackages/core/package.json:@rat-stack/core
Rename the bin entry in apps/cli/package.json, the root command in apps/cli/src/command.ts, and the version in apps/cli/src/version.ts. Then change every matching import. The current workspace imports are @rat-stack/capability, @rat-stack/cli, @rat-stack/core, @rat-stack/infra, and @rat-stack/site. Find them with:
rg -n '@rat-stack/' --glob '*.json' --glob '*.ts' -lA new scope changes line lengths, so run the formatter once after the rename:
pnpm fixReplace inspectFile in packages/core with one useful capability. Add it to capabilities, then verify each surface you keep. Rewrite the Project law, Architecture, and Boundaries sections of AGENTS.md and the top of .pi/APPEND_SYSTEM.md; they describe rat-stack until you do. Keep expected failures typed and map them to deliberate exit codes.
apps/site is the public site and Worker. apps/infra is its Alchemy Stack. Delete both if you do not want a public site. If you keep them, change the Cloudflare Zone name and the DNS names and targets derived from it in apps/infra/alchemy.run.ts. Change the Worker domain and redirects in apps/site/src/worker.ts. Choose an Alchemy stage for each plan or deploy. The stage is a command-line choice, for example --stage <stage>. Configure credentials with:
pnpm alchemy profile edit --add CloudflareRun the acceptance gate before the first push:
pnpm turbo run check test build- One capability reaches every kept surface. Proof:
packages/capability/test/to-command.test.ts,packages/capability/test/to-http-api.test.ts,packages/capability/test/to-toolkit.test.ts,packages/capability/test/to-code-mode.test.ts, andapps/cli/test/cli.e2e.test.ts. - Schemas are the contract. Proof:
packages/capability/test/catalog.test.ts,packages/capability/test/to-http-api.test.ts, andpackages/capability/test/to-toolkit.test.tscheck derived JSON Schema and typed failures. - A fresh sandbox isolates model code. The Node sandbox has a 10-second wall-clock limit and a 1-second limit per synchronous evaluation. Both run inside the child. Scope close and interruption send SIGTERM, then SIGKILL after 250 milliseconds. Stdin EOF, a lost parent, or a failed protocol write ends the child. Child-process imports remain blocked. Proof:
packages/capability/test/sandbox.test.tschecks isolation and VM budgets;sandbox-lifetime.test.tsandsandbox-orphan.test.tscheck deadlines, parent death, and process-group cleanup. - Rate limits fail closed. Proof:
apps/site/test/worker.test.tschecks 429 responses before another execute worker starts and when the global limit denies. - Markdown is the default machine representation. Proof:
apps/site/test/worker.test.tschecks Markdown withoutAccept: text/htmland HTML only when requested.
The template is a working project. Delete what you will not use on day one; the fence will identify dependent code that must go too. After each cut, trim packages/capability/src/index.ts and run pnpm install, pnpm fix, then pnpm turbo run check test build. Update the package list in AGENTS.md.
- Only the CLI:
- Keep:
packages/capability/src/contract.ts,packages/capability/src/implement.ts,packages/capability/src/to-command.ts,call-watch.ts,actor-watch.ts, every dependency imported by retained code, and their tests; all ofpackages/core; thetoCommandcomposition inapps/cli/src/surfaces.ts - Delete: unused projections and their matching tests after tracing the retained imports; unused compositions in
apps/cli/src/surfaces.ts; thecatalog,openapi,serve, andmcpcommands inapps/cli/src/command.ts;apps/cli/test/serve.test.tsand the MCP and catalog cases inapps/cli/test/cli.e2e.test.ts
- Keep:
- No code mode:
- Delete:
packages/capability/src/catalog.ts,packages/capability/src/code-mode.ts,packages/capability/src/sandbox-error.ts,packages/capability/src/sandbox-service.ts,packages/capability/src/sandbox-subprocess.ts, andpackages/capability/src/to-code-mode.ts;packages/capability/test/catalog.test.ts,packages/capability/test/sandbox.test.ts,packages/capability/test/sandbox-lifetime.test.ts,packages/capability/test/sandbox-orphan.test.ts,packages/capability/scripts/sandbox-lifetime-proof.mjs, andpackages/capability/test/to-code-mode.test.ts; the./sandboxand./code-modeexports inpackages/capability/package.json;codeModeandmcpServer.codeModeinapps/cli/src/surfaces.ts; thecatalogcommand and the--code-modeflag inapps/cli/src/command.ts; the code-mode and catalog cases inapps/cli/test/cli.e2e.test.ts
- Delete:
- No HTTP:
- Delete:
packages/capability/src/to-http-api.ts,packages/capability/src/http-api.ts, and their tests; the./http-apiexport inpackages/capability/package.json;http,routes, andwebServerinapps/cli/src/surfaces.ts; theopenapiandservecommands;apps/cli/test/serve.test.ts; the OpenAPI case inapps/cli/test/cli.e2e.test.ts
- Delete:
- No MCP:
- Delete:
packages/capability/src/to-toolkit.ts,packages/capability/src/toolkit.ts, their test, andpackages/capability/test/mcp-harness.ts; the./toolkitexport inpackages/capability/package.json;toolsandmcpServerinapps/cli/src/surfaces.ts; themcpcommand; the MCP cases inapps/cli/test/cli.e2e.test.ts. Code mode imports frompackages/capability/src/to-toolkit.ts, so cutting MCP cuts code mode too
- Delete:
- Default flags only:
- Keep: Core's
Flagsport and declarations - Delete: Replace
apps/site/src/flags.tswith core imports andexport const flagsLayer = Flags.defaults(eventFlags); export const liveFlagsLayer = flagsLayer;; removepackages/flagsand its dependency inapps/site/package.json. Readers use defaults; the CLI writer fails with a named read-only error
- Keep: Core's
- No analytics:
- Delete:
packages/events; the@rat-stack/eventsline inapps/site/package.json; the code excerpts that quotepackages/events/testin.brain/resources/lore/tests-that-earn-their-place.svxandskills/test-behaviour/SKILL.md - Change: the one line in
apps/site/src/events.tsbecomesexport { events, memoryEvents } from "@rat-stack/core/events-off"; - Then:
pnpm install,pnpm git-snapshot, and the gate - Optional:
EVENTS_ENABLED,EVENTS_SINK_TOKEN, andEVENTS_IDENTITY_MODEinapps/site/.env.schemaandapps/site/src/config-fingerprint.ts, and the events flags inpackages/core/src/flags.ts. The Worker ignores them once capture is off. Details:packages/events/README.md
- Delete:
- No XState (only after removing interest and every other lifecycle except file inspection):
- Keep: Keep XState and its bridge while any retained lifecycle imports them
- Delete:
packages/core/src/inspect-machine.tsand its test (callFileInspector.inspectdirectly frompackages/core/src/inspect-file.ts);xstateand@xstate/effectinpackages/core/package.jsonand theirminimumReleaseAgeExcludeentries inpnpm-workspace.yaml;scripts/oxlint-plugin-xstate-effect.tsand its entry inoxlint.config.ts
defineContract, implement, and toCommand are the minimum that keep stats working. The contract module has no dependency on the other projections.
MIT