rat-stack 🐀

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 surface23. The same inspectFile capability is the stats command, POST /inspectFile with an OpenAPI document, and an MCP tool over stdio.

Create a repository

gh repo create <name> --template joelhooks/rat-stack

Clone the new repository, then install and verify it:

nvm use
pnpm install
pnpm vendor:agent-sources
pnpm turbo run check test build

Node 24.18.0 and pnpm 11.3.0 are required. The requirement is declared in .nvmrc, engines, devEngines, and packageManager.

Workspace layout

PathPackageRole
apps/cli@rat-stack/cliComposition root: stats, openapi, serve, mcp
packages/capability@rat-stack/capabilitydefineContract, implement, and the toCommand, toHttpApi, toToolkit, toRpc, and toCodeMode projections
packages/core@rat-stack/coreDomain example: the inspectFile contract, handler, and lifecycle machine
packages/flags@rat-stack/flagsTyped flags, Config, memory and KV adapters, stable rollouts, audited writes, and removal dates
packages/events@rat-stack/eventsRequest analytics with an EventSink port and Basin resources
packages/lore@rat-stack/lorePublic content graph and traversal service
packages/subscriber-delivery@rat-stack/subscriber-deliveryExternal subscriber intake and confirmation adapter behind core’s job-shaped ports
apps/infra@rat-stack/infraAlchemy Stack (Cloudflare by default)
apps/site@rat-stack/siteCloudflare Worker for the public site, agent discovery, and sandboxed execute
.agent_sources/—Shallow upstream mirrors (gitignored clones; see README there)

Try the example CLI

The included stats command counts bytes, Unicode characters, words, and lines in a file:

pnpm cli stats README.md
pnpm cli stats README.md --json

After pnpm build, you can run the built entrypoint directly:

node apps/cli/dist/cli.js stats README.md

The 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 sees

Code mode21 is the fourth projection. The model gets search for ranked matches with TypeScript signatures and execute for JavaScript with tools in scope20. 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 capabilities21, then verify every projection you keep; CLI command registration lives in apps/cli/src/command.ts.

What is in the stack?

Every dependency is pinned exactly. Review pin upgrades as stack changes.

Commands

CommandPurpose
pnpm typecheckCheck TypeScript without emitting files
pnpm checkTypecheck, check formatting, and run type-aware linting
pnpm fixApply Oxfmt and safe Oxlint fixes
pnpm testRun the Vitest suite once
pnpm buildCompile package outputs
pnpm cli -- ...Build and run the CLI
pnpm vendor:agent-sourcesClone core lib mirrors
pnpm turbo run check test buildCached verification pipeline

Agentic surface

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/.

Make it yours

Start with the template:

gh repo create <name> --template joelhooks/rat-stack

Clone the new repository, then install it:

pnpm install
pnpm sources:fetch

pnpm sources:fetch imports missing pinned source9 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:

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' -l

A new scope changes line lengths, so run the formatter once after the rename:

pnpm fix

Replace inspectFile in packages/core with one useful capability. Add it to capabilities21, 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 Cloudflare

Run the acceptance gate before the first push:

pnpm turbo run check test build

What it promises and how to check

Keep or cut

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 table in AGENTS.md.

WantKeepDelete
Only the CLIpackages/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 of packages/core; the toCommand composition in apps/cli/src/surfaces.tsunused projections and their matching tests after tracing the retained imports; unused compositions in apps/cli/src/surfaces.ts; the catalog, openapi, serve, and mcp commands in apps/cli/src/command.ts; apps/cli/test/serve.test.ts and the MCP and catalog cases in apps/cli/test/cli.e2e.test.ts
No code modepackages/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, and packages/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, and packages/capability/test/to-code-mode.test.ts; the ./sandbox and ./code-mode exports in packages/capability/package.json; codeMode and mcpServer.codeMode in apps/cli/src/surfaces.ts; the catalog command and the --code-mode flag in apps/cli/src/command.ts; the code-mode and catalog cases in apps/cli/test/cli.e2e.test.ts
No HTTPpackages/capability/src/to-http-api.ts, packages/capability/src/http-api.ts, and their tests; the ./http-api export in packages/capability/package.json; http, routes, and webServer in apps/cli/src/surfaces.ts; the openapi and serve commands; apps/cli/test/serve.test.ts; the OpenAPI case in apps/cli/test/cli.e2e.test.ts
No MCPpackages/capability/src/to-toolkit.ts, packages/capability/src/toolkit.ts, their test, and packages/capability/test/mcp-harness.ts; the ./toolkit export in packages/capability/package.json; tools and mcpServer in apps/cli/src/surfaces.ts; the mcp command; the MCP cases in apps/cli/test/cli.e2e.test.ts. Code mode imports from packages/capability/src/to-toolkit.ts, so cutting MCP cuts code mode too
Default flags onlyCore’s Flags port and declarationsReplace apps/site/src/flags.ts with core imports and export const flagsLayer = Flags.defaults(eventFlags); export const liveFlagsLayer = flagsLayer;; remove packages/flags and its dependency in apps/site/package.json. Readers use defaults; the CLI writer fails with a named read-only error
No analyticspackages/events; @rat-stack/events in apps/site/package.json; the Basin and basinFoundation calls, EVENTS_ENABLED, withEventCapture, inBackground, EVENTS_IDENTITY_MODE, and the events parameter in apps/site/src/worker.ts; EVENTS_ENABLED, EVENTS_SINK_TOKEN, and EVENTS_IDENTITY_MODE in apps/site/.env.schema and apps/site/src/config-fingerprint.ts; apps/site/test/worker-events.test.ts
No XState (only after removing interest and every other lifecycle except file inspection)Keep XState and its bridge while any retained lifecycle imports thempackages/core/src/inspect-machine.ts and its test (call FileInspector.inspect directly from packages/core/src/inspect-file.ts); xstate and @xstate/effect in packages/core/package.json and their minimumReleaseAgeExclude entries in pnpm-workspace.yaml; scripts/oxlint-plugin-xstate-effect.ts and its entry in oxlint.config.ts

defineContract, implement, and toCommand are the minimum that keep stats working. The contract module has no dependency on the other projections.

License

MIT


Last changed 2026-10-12 in 2c5b496. Source on GitHub. Change log.

Linked from

  1. Agent rules for rat-stack (AGENTS.md). What you may change, which commands to run, and which changes need approval.
  2. Effect + Alchemy peer patterns. Source-grounded patterns from repos on nearby Effect and Alchemy pins.
  3. gardener. Keep rat-stack current and clean. Update exact dependency versions, learn from repos on the same versions, and add lint rules before cleanup.
  4. Learn mode presents concepts at useful depths. An agent presents source-derived concepts during real work and keeps learning events in a private local log.