Learn the stack
Start with inspectFile, a capability21: one named action with a contract and a server-side handler. The contract21 defines its name, input, output, failure, and metadata. The handler implements the action.¶
A service is a named interface for one job. A machine defines states and the events that permit movement between them. A projection18 builds a callable interface from capabilities.¶
Follow these parts, then try the hosted content tools.¶
Read Effect basics17 before this trace if the computation model is new. For implementation tasks, use services16, Layer composition13, and testing22.¶
The pieces
- Effect describes work with typed failures36 and required dependencies.¶
- Schema defines accepted values and their runtime checks.¶
- A Layer builds services and supplies their construction dependencies.¶
- XState owns finite lifecycles. The Effect bridge runs their declared actors.¶
- TypeScript 7 and Effect diagnostics check types and Effect usage.¶
- Oxlint, Oxfmt, Vitest, and lefthook enforce the fence39: checks and hooks that reject prohibited code and shortcuts.¶
- pnpm and Turborepo connect packages and cache checks.¶
- Alchemy declares the cloud. Follow learn Alchemy for the deployment graph.¶
One action, five interfaces
┌─ CLI
├─ HTTP
One Capability ──────┼─ MCP
├─ code mode
└─ RPC A projection18 exposes the same action through another interface. RPC serves browser clients. Code mode21 lets agents compose calls in one program.¶
All five interfaces use one handler.¶
Trace one action
The CLI uses inspectFile. The hosted site uses a separate content registry.¶
1. Define the contract
The contract21 names the action and its input, output, and failure schemas.¶
export const inspectFileContract = defineContract("inspectFile", {
annotations: { idempotent: true, readOnly: true },
description: "Count bytes, characters, words, and lines in a file",
failure: FileStatsError,
input: Schema.Struct({
path: Schema.String.annotate({ description: "File to inspect" }),
}),
output: FileStatsSchema,
});FileStatsError belongs to the interface. The annotations mark a repeatable, read-only action.¶
2. Bind one handler
implement binds the contract to runInspectMachine. The registry includes the resulting capability21.¶
export const inspectFile = implement(inspectFileContract, ({ path }) =>
runInspectMachine(path)
);
export const capabilities = [inspectFile, joinInterest] as const;The handler starts a machine to read the file.¶
3. Put file work in a service
FileInspector.make captures the file system. Its inspect method reads bytes and maps file errors to FileStatsError.¶
make: Effect.gen(function* makeFileInspector() {
const fileSystem = yield* FileSystem.FileSystem;
const inspect = Effect.fn("FileInspector.inspect")(function* inspect(
path: string
) {
const content = yield* fileSystem
.readFile(path)
.pipe(
Effect.mapError(
(error) => new FileStatsError({ path, reason: error.message })
)
);
return summarizeBytes(path, content);
});The service exposes its Layer12 beside the implementation. This follows hexagonal architecture19: composition supplies the file-system dependency.¶
return { inspect } as const;
}),
}) {
static readonly layer = Layer.effect(this, this.make);
}Callers ask the service to inspect a path. Composition chooses the file-system implementation.¶
4. Give the lifecycle named states
A declared fromEffect actor calls FileInspector.¶
const readStats = fromEffect({
effect: ({ input }) =>
FileInspector.use((inspector) => inspector.inspect(input.path)),
schemas: { input: Schema.Struct({ path: Schema.String }) },
});The machine starts in reading. It finishes in inspected or unreadable.¶
initial: "reading",
output: ({ context }) => context.outcome,
states: {
inspected: { type: "final" },
reading: {
invoke: {
input: ({ context }) => ({ path: context.path }),
onDone: {
context: ({ event }) => ({
outcome: inspectOutcome.Inspected({ stats: event.output }),
}),
target: "inspected",
},
onError: {
context: ({ event }) => ({
outcome: inspectOutcome.Unreadable({ error: event.error }),
}),
target: "unreadable",
},
src: "readStats",
},
},
unreadable: { type: "final" },
},
});5. Project the registered actions
The command-line interface (CLI) projects the same capabilities21 list for each server interface.¶
export const http = toHttpApi("RatStack", capabilities);
export const tools = toToolkit(capabilities);
export const codeMode = toCodeMode(capabilities);
export const routes = Layer.merge(
HttpApiBuilder.layer(http.api, { openapiPath: "/openapi.json" }).pipe(
Layer.provide(http.layer)
),
HttpApiScalar.layer(http.api, { path: "/docs" })
);The RPC projection18 derives contracts from its supplied capabilities.¶
export const toRpc = <const Caps extends readonly AnyCapability[]>(
capabilities: Caps
): RpcProjection<Caps> => {
const contracts = capabilities.map((capability) => capability.contract);A projection takes a registry and reuses its domain handlers.¶
6. Connect the CLI
toCommand derives each command. inspectFile also gets the stats alias.¶
const capabilityCommands = capabilities.map((capability) => {
const command =
capability === inspectFile
? toCommand(capability, {
positional: ["path"],
render: formatFileStats,
}).pipe(Command.withAlias("stats"))
: toCommand(capability);
return command;
});The entry point supplies the service Layer12 and Node services. It denies approval21 by default.¶
Effect.provide(
Layer.mergeAll(
Layer.provideMerge(FileInspector.layer, NodeServices.layer),
remoteJoinInterestLayer,
Approval.denyAll
)
)The command keeps the shared handler.¶
7. Compose the cloud
An Alchemy Stack groups one cloud infrastructure program and its outputs. Alchemy declares the Stack, Cloudflare providers, and state. The Stack includes Mischief and Website.¶
export default Alchemy.Stack(
"RatStack",
{
providers: Cloudflare.providers(),
state: Cloudflare.state(),
},
Effect.gen(function* stack() {
const mischief = yield* Mischief;
const website = yield* Website;
return { mischiefUrl: mischief.url, websiteUrl: website.url };
})
);The Stack deploys the cloud applications. The Node file-inspection CLI runs separately.¶
Try hosted search and read
Use POST /api/execute for code mode21. The agent guide documents this search → read program:¶
const found = await tools.search({ query: "cartridges", limit: 1 });
const page = await tools.read({ id: found.matches[0].id });
return { title: page.title, id: page.id }; Send it as a JSON code string:¶
curl --request POST 'https://ratstack.sh/api/execute' \
--header 'content-type: application/json' \
--data '{"code":"const found = await tools.search({ query: \"cartridges\", limit: 1 }); const page = await tools.read({ id: found.matches[0].id }); return { title: page.title, id: page.id };"}' read takes the exact ID from search. This hosted program reads site content.¶
Read before changing a piece
- Read
AGENTS.mdfor pins, boundaries, and the fence39.¶ - Read VISION.md for intent.¶
- Read the package you plan to change.¶
- Before Effect or XState work, read
node_modules/effect/AGENTS.mdand the pinned sources listed in repo law.¶
In a copied product repo, replace the template’s product notes and rules. Keep the stack lessons that still serve the product.¶
Follow repo law and pinned code when an example is stale.¶
Pick the next skill
- Add schemas and a shared action:
add-a-capability.¶ - Model a lifecycle: add a lifecycle machine.¶
- Remove an interface: keep or cut.¶
- Follow the cloud footprint: learn Alchemy.¶
Finish
Run the full gate:¶
pnpm turbo run check test build Fix failures. Keep the fence39 intact.¶
Check deployment separately from the gate.¶
Last changed 2026-10-07 in f1a00f2. Source on GitHub. Change log.
Unlinked mentions (14)
- add-a-capability → The CLI, HTTP, MCP, RPC, and code-mode projections take implemented capabilities.
- Agent rules for rat-stack (AGENTS.md) → CLI, HTTP, MCP, RPC, and code mode are projections in packages/capability.
- Auth → RPC means remote procedure call: a client calls a named operation on the server.
- CAFE names the Cloudflare, Alchemy, Foldkit and Effect stack → PR #6 moves chat events to Effect RPC over a Cloudflare.RpcDurableObject, one per chat.¶
- Capabilities → RPC calls.
- Devtools → The local web development backend records calls and serves the overlay, RPC, and MCP tools under /__rat.
- Hexagonal architecture → One capability, every surface starts with a contract, binds a handler, and projects CLI, HTTP, MCP, RPC, and code mode.
- Hosted agent interfaces → RPC means remote procedure call: a client calls a named operation on the server.
- Infrastructure as effects → Projections turn registered capabilities into command line, HTTP, MCP tools, browser RPC, and code mode surfaces.
- One capability, every surface → The interfaces are command line, HTTP, MCP tools, browser RPC, and code mode.
- Schema projections: 2026-09-18 history → Read One capability, every surface for today’s defineContract → implement pattern, including browser RPC, and current pins.
- Tests prove behavior through supplied services → Our projection tests execute the produced CLI command, HTTP route, MCP tool, RPC call, or sandbox call.
- uncomplect → RPC lets a client call named server operations.¶
- use-a-prompt → The same contracts project to CLI, HTTP, MCP tools, RPC, and code mode.
Content updated .