Alchemy
Use this skill when configuring, developing, deploying, or debugging infrastructure with Alchemy v2 and the Effect-native patterns from alchemy-run/alchemy-effect. Alchemy treats infrastructure as an Effect program: stacks declare resources, providers supply cloud integrations, local dev can run app code locally against real cloud resources, and deploys converge declared resources into live cloud state.
Alchemy is the preferred Darkmatter path for new TypeScript/Effect deployable infrastructure. Reach for it before ad hoc provider scripts, hand-written Wrangler/Terraform glue, or one-off deploy workflows unless a repo has an explicit exception or an existing production system that should not be disturbed.
Alchemy v2 and alchemy-effect move quickly. Verify upstream docs before changing deploy or dev code, especially package versions, provider APIs, alchemy dev behavior, and CLI flags. This skill carries the upstream alchemy-run/alchemy-effect repo as a local submodule at reference/alchemy-effect for source, examples, and agent instructions.
When to use
- The user asks to set up or review an
alchemy.run.tsfile. - The user asks to develop with
alchemy dev, local Workers, public preview URLs, hot reload, or webhook testing. - The user asks to configure an Alchemy deploy for Cloudflare Workers, Vite apps, R2, D1, Queues, Durable Objects, AWS Lambda, S3, DynamoDB, ECS, EC2, static sites, or related resources.
- The user asks how to use Alchemy stages, profiles, state stores, bindings, stack outputs, cross-stack references, or CI/CD.
- The user asks to migrate deploy plumbing toward Alchemy v2 or away from ad hoc provider scripts.
- The user asks for examples from
alchemy-run/alchemy-effect/examples.
When NOT to use
- The request is about the blockchain data/API company Alchemy and not
alchemy.run. - The project does not use TypeScript, Effect, Bun/Node, or Alchemy, and the user is only asking a generic deployment question.
- The task is to implement or modify a provider inside the upstream
alchemy-effectrepo. Use the upstream repo'sAGENTS.mddirectly, then use this skill only for deploy-configuration implications. - The task is purely Effect application logic with no deployable infrastructure. Use
effect-typescript. - A mature repo already has a production deploy path and the user asks for a narrow bug fix. Preserve the working path unless they explicitly ask to migrate.
Upstream check
Before editing, inspect the current docs or local installed package:
bun alchemy --help
bun alchemy dev --help
bun pm ls alchemy effect @effect/platform-bun @effect/platform-node
Use these upstream references first:
https://v2.alchemy.run/getting-started/for the current install command, first stack shape, and deploy flow.https://v2.alchemy.run/concepts/stack/for stack names, outputs, stages, cross-stack references, and state isolation.https://v2.alchemy.run/concepts/local-development/andhttps://v2.alchemy.run/tutorial/part-4/foralchemy dev.https://v2.alchemy.run/concepts/state-store/for where state is persisted.https://v2.alchemy.run/guides/ci/for non-interactive deploys and PR preview environments.reference/alchemy-effect/AGENTS.mdfor upstream Effect-native infrastructure conventions.reference/alchemy-effect/README.mdfor the current project overview and install shape.reference/alchemy-effect/examples/for concrete app patterns.reference/alchemy-effect/packages/alchemy/src/for provider/resource implementation patterns.
If upstream conflicts with this skill, follow upstream and mention the drift.
If the submodule is missing in a fresh checkout, initialize it before relying on local references:
git submodule update --init skills/alchemy/reference/alchemy-effect
Treat the submodule as read-only during app/deploy work. Edit it only when the user explicitly asks for an upstream alchemy-effect contribution, and then follow reference/alchemy-effect/AGENTS.md as the repo-local authority.
Project setup
Prefer Bun unless the project already standardizes on another package manager. Current Alchemy v2 docs recommend Bun or Node.js 22+ and install Alchemy with Effect platform packages:
bun add "alchemy@2.0.0-beta.40" "effect@>=4.0.0-beta.66 || >=4.0.0" "@effect/platform-bun@>=4.0.0-beta.66 || >=4.0.0" "@effect/platform-node@>=4.0.0-beta.66 || >=4.0.0"
Pin package versions according to the existing repo policy. If a repo already uses a catalog, workspace protocol, or lockfile-only upgrade lane, follow that instead of pasting the docs command blindly.
Before adding Alchemy to an existing repo, inspect the current setup:
rg -n "alchemy|alchemy.run|alchemy dev|alchemy deploy" package.json bun.lock package-lock.json pnpm-lock.yaml yarn.lock .github scripts . --glob '!node_modules'
find . -name 'alchemy.run.ts' -o -name 'alchemy.run.js'
If the repo does not already have Alchemy configured, ask the user whether they want Alchemy set up for local development as well as deploys. Do not silently create a deploy-only setup: alchemy dev changes the local workflow by provisioning real cloud resources, exposing public preview URLs, and enabling webhook/OAuth callback testing against local code. If the user says yes, include the dev script, dev-stage/profile guidance, public preview output handling, and webhook test notes in the first implementation.
Add scripts that make the stack path explicit when the repo has more than one app:
{
"scripts": {
"dev": "alchemy dev ./alchemy.run.ts",
"deploy": "alchemy deploy ./alchemy.run.ts",
"destroy": "alchemy destroy ./alchemy.run.ts",
"deploy:prod": "alchemy deploy ./alchemy.run.ts --stage prod --profile prod"
}
}
Use plain bun alchemy deploy only in tiny repos where alchemy.run.ts at the root is unambiguous.
Org default
For Darkmatter TypeScript/Effect projects, prefer Alchemy for deployable infrastructure:
- New Cloudflare Workers, Vite/frontends, queues, D1/R2/KV, workflows, AI Gateway, tunnels, and GitHub deploy automation should start with Alchemy.
- New AWS Lambda, S3, DynamoDB, API Gateway, ECS/EC2/EKS/RDS, and static-site deploys should consider Alchemy first.
- Shared preview environments should use Alchemy stages instead of bespoke name mangling.
- Local webhook testing should use
alchemy devor an Alchemy-managed tunnel/preview flow instead of separate ngrok/Cloudflare Tunnel scripts when Alchemy can provide the route. - Hand-written provider CLIs can still be useful for diagnostics, but should not become the durable deploy control plane without a reason.
Stack shape
Every deploy file should default-export Alchemy.Stack(name, options, Effect.gen(...)).
import * as Alchemy from "alchemy";
import * as Cloudflare from "alchemy/Cloudflare";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"MyApp",
{
providers: Cloudflare.providers(),
state: Cloudflare.state(),
},
Effect.gen(function* () {
const bucket = yield* Cloudflare.R2Bucket("Bucket");
return {
bucketName: bucket.bucketName,
};
}),
);
The load-bearing pieces are:
- Stack name: stable logical deployment unit. Do not casually rename it; state and names derive from it.
- Providers: cloud auth and provider implementations, for example
Cloudflare.providers()orAWS.providers(). - State: choose deliberately. Cloudflare deploys commonly use
Cloudflare.state(); simple AWS examples may useAlchemy.localState(). - Effect body: declare resources with
yield*, compose dependencies through references and bindings, and return useful outputs for smoke checks.
Effect-native provider module layout
When authoring or refactoring an Alchemy provider in a Darkmatter app/repo, mirror the canonical alchemy-run/alchemy-effect/packages/alchemy/src/Cloudflare organization instead of putting resource contracts, HTTP clients, schemas, helpers, and provider lifecycle in one large file. Before writing provider code, inspect the relevant upstream provider directory (for example Cloudflare/Providers.ts, Cloudflare/KV/KVNamespace.ts, and adjacent *Binding.ts/index.ts files) and keep the same separation of concerns.
Canonical shape for a provider namespace such as src/Verda/:
ResourceName.tscontains only the publicResourcetype, props, attributes, JSDoc/examples, andResource<ResourceName>("Namespace.ResourceName"). It should not contain the HTTP client or reconcile helpers.ResourceNameProvider.tscontainsProvider.effect(ResourceName, Effect.gen(...))and the lifecycle methods (read,diff,reconcile,delete). Keep provider-local adoption/reconcile logic readable and delegate API calls/selection/status helpers to sibling modules.Providers.tsdefinesclass Providers extends Provider.ProviderCollection<Providers>()("Namespace") {},ProviderRequirements, andproviders(). TheProvider.collection([...])list contains resource tokens/policies (for exampleGpuInstance), while implementation layers are supplied with.pipe(Layer.provide(...)), matching Cloudflare'sProviders.ts. Do not put already-provided provider layers insideProvider.collection.Client.ts/ provider SDK modules expose EffectContext.Serviceclients andLayer.effectlive implementations. Decode all unknown provider responses withSchemaat this boundary and return typed domain objects.Config.ts/Credentials.tscentralizeConfig.string,Config.redacted, defaults, and auth/profile resolution. Secrets stay redacted and are provided through layers, not resource props or physical names.Errors.ts,Types.ts,Wire.ts,Mapping.ts,Status.ts,Selection.ts, and other focused helpers are preferred over multi-hundred-line resource files when they isolate typed errors, wire schemas, pure selection logic, and domain mapping.index.tsre-exports the namespace's public surface so app code imports./ProviderNamespace/index.tsrather than a monolithic resource file.
Testing convention for provider refactors: extract pure helpers where possible (selection, name normalization, status classification, mapping) and write focused Vitest tests for them (beside the source: Selection.test.ts next to Selection.ts) before moving production code. For Effect-dependent provider lifecycle, test through Layers/fakes where practical; otherwise typecheck with bun tsc -b and avoid changing behavior during layout-only refactors.
Local development
Use alchemy dev for the default local loop when the app can run through Alchemy:
bun alchemy dev ./alchemy.run.ts
Alchemy dev mode deploys infrastructure to the real cloud provider, runs supported app code locally, and hot reloads on file changes. For Cloudflare Workers, the Worker runs locally in workerd while a cloud proxy routes requests between public cloud endpoints and the local process.
This matters for webhook work: external services need a public URL, while the developer needs local code, breakpoints, and hot reload. Prefer an Alchemy dev/preview URL for webhook callbacks when available, then configure the third-party webhook target to that URL for the session or preview stage.
Use explicit dev ports when the app has multiple local services or a fixed callback URL:
export const Worker = Cloudflare.Worker("Worker", {
main: "./src/worker.ts",
dev: {
port: 3000,
},
});
Dev-mode rules:
- Treat cloud resources created by
alchemy devas real resources, not emulators. - Keep stage/profile explicit when working near shared accounts.
- Return local/public URLs from stack outputs so the agent can test the live route.
- Verify webhook flows with the provider's actual delivery logs when available.
- Stop the dev process cleanly; destroy dev/preview resources when they are no longer needed and the stack owns them.
Stage and profile rules
Treat stage and profile as part of the deployment contract:
- Stage isolates state and physical names. The default is developer-specific; use explicit
--stage prod,--stage staging, or--stage pr-123for shared environments. - Profile selects credentials. First deploy may prompt interactively and save credentials under
~/.alchemy/profiles.json; non-interactive CI must have profiles or provider credentials prepared before deploy. - Production scripts should pass both
--stage prodand the intended--profile. - Preview deploys should derive a deterministic stage from the PR, branch, or issue id and should destroy that stage when it is no longer needed.
Useful commands:
bun alchemy deploy ./alchemy.run.ts --stage dev_cm
bun alchemy deploy ./alchemy.run.ts --stage prod --profile prod
bun alchemy destroy ./alchemy.run.ts --stage pr-123 --profile preview
alchemy login --configure
alchemy login --profile prod --configure
Never use Date.now() or random timestamps in physical names. Let Alchemy generate names from stack, stage, and logical id, or use a deterministic name tied to the environment.
Bindings and runtime dependencies
Prefer bindings over manually threading deployed identifiers through environment variables. A binding is deploy-time data attached to a runtime resource so the function/worker receives exactly the infrastructure dependency it needs.
For Cloudflare, bind resources directly into a Worker or Vite app:
export const DB = Cloudflare.D1Database("DB");
export const Bucket = Cloudflare.R2Bucket("Bucket");
export const Worker = Cloudflare.Worker("Worker", {
main: "./src/worker.ts",
bindings: {
DB,
Bucket,
},
});
For Effect-native runtimes, keep runtime services and deploy wiring separate. Alchemy owns cloud resources and bindings; Effect services own runtime behavior. Use Layers at the stack boundary when the resource requires a runtime implementation.
Capability shape: Binding.Service + Binding.Policy
Every Alchemy capability ships as a pair of layers:
Binding.Service— runtime SDK wrapper, provided on the Function/Worker Effect (bundled into the deployed artifact).Binding.Policy— deploy-time IAM/binding attachment, provided on the Stack viaAWS.providers()(), never bundled.
For Cloudflare, the worker bindings: { ... } field handles both sides automatically. For AWS, the Service layer goes on the Function and the Policy layer must be enabled at the stack level. The default AWS.providers() already enables the common policy layers; only reach for AWS.providers()(extraLayers) when you author or override a capability.
Alchemy.RuntimeContext on runtime-only methods
Bindings expose a typed callable whose inner Effect carries an Alchemy.RuntimeContext requirement. Treat that as a colored function:
- The outer Effect (setup at function init) does NOT require
RuntimeContext. - The inner Effect (the actual SDK call) MUST require
RuntimeContextand only runs inside a deployed Function/Worker.
If a service wraps a binding and accidentally leaks WorkerEnvironment / Lambda.FunctionEnvironment into its interface, that service becomes cloud-coupled. Resolve cloud env services once during Layer construction and close over them — do not expose them as requirements on consumer Effects.
Effect platform rules in app/runtime code
Alchemy stacks and Worker/Function bodies run as Effect programs. Do not reach for raw async or Node primitives inside Effect.gen:
| Don't | Do |
|---|---|
import fs from "node:fs/promises" |
const fs = yield* FileSystem.FileSystem |
await fs.readFile(p, "utf8") |
yield* fs.readFileString(p) |
import path from "pathe" / node:path |
const path = yield* Path.Path |
await fetch(...) |
yield* HttpClient.HttpClient + HttpClientRequest |
new Promise((res) => setTimeout(res, ms)) |
yield* Effect.sleep(Duration.millis(ms)) |
Effect.promise(() => listSqlFiles(dir)) |
Make the helper itself return an Effect |
Sync, CPU-only Node APIs (crypto.createHash, process.cwd, Buffer, TextEncoder) must still be wrapped in Effect.sync(() => …) (or Effect.try if they can throw) so the call participates in the Effect runtime — tracing, interruption, and the error channel all depend on it.
const hash = yield * Effect.sync(() => crypto.createHash("sha256").update(input).digest("hex"));
const cwd = yield * Effect.sync(() => process.cwd());
This applies to stack bodies, custom resource helpers, and tests. Tests must use FileSystem.FileSystem / Path.Path for any file/path access.
Polling rules for deploy/runtime tests:
- Use
Effect.repeatwithSchedule+ anuntilpredicate and a boundedtimes: Ncap. - Do not write
while (Date.now() < deadline)loops — they ignore interruption and leak into the vitest timeout.
// good — declarative, bounded, interruption-safe
const value =
yield *
fetchValue.pipe(
Effect.repeat({
schedule: Schedule.spaced("5 seconds"),
until: (v) => v.ready,
times: 36,
}),
);
Provider lifecycle tests
When authoring custom Alchemy providers/resources, add provider-style tests with alchemy/Test/Vitest rather than only unit-testing helper functions. These tests exercise the real Alchemy plan/apply engine, private scratch state, repeated deploys, and destroy behavior in one Effect program.
Use this shape for resource lifecycle tests:
import * as Test from "alchemy/Test/Vitest";
import { expect } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as ProviderNamespace from "../src/ProviderNamespace/index.ts";
const { test } = Test.make({ providers: ProviderNamespace.providers() });
test.provider("create, update, and delete resource", (stack) =>
Effect.gen(function* () {
const client = yield* ProviderNamespace.Client;
const created = yield* stack.deploy(
Effect.gen(function* () {
return yield* ProviderNamespace.Resource("TestResource", {
name: "v1",
});
}),
);
expect(created.resourceId).toBeDefined();
// Verify live provider state through the provider SDK/client, not only
// through returned attributes.
const live1 = yield* client.get(created.resourceId);
expect(live1.name).toBe("v1");
const updated = yield* stack.deploy(
Effect.gen(function* () {
return yield* ProviderNamespace.Resource("TestResource", {
name: "v2",
});
}),
);
expect(updated.resourceId).toBe(created.resourceId);
const live2 = yield* client.get(updated.resourceId);
expect(live2.name).toBe("v2");
yield* stack.destroy();
}),
);
Provider test rules:
- Configure providers once per file with
const { test } = Test.make({ providers: Namespace.providers() }). - Prefer real provider SDK/client assertions for integration tests. If live credentials or real resources are expensive/dangerous, use a fake service layer but still run through
test.provider,stack.deploy(...), repeated deploy, andstack.destroy(). - Test create → update/noop/replace as appropriate → destroy in the same
test.providerbody so Alchemy state is shared across deploys. - Use the scratch stack handed to the test; do not write to
.alchemy/or shared state from provider tests. - Provider implementations should close over services at provider construction (
const client = yield* Clientbefore returning lifecycle methods), matching Cloudflare providers. Do not leaveyield* Clientrequirements insideread/diff/reconcile/deleteunless the effect is explicitly provided there;test.providershould catch this with a “Service not found” failure. - For live provider tests that need credentials, resolve them through the same AuthProvider/config path as
alchemy deploy(for example env-method credentials or analchemy logintest profile). Skip or gate the test when credentials are absent; do not commit secrets. - Keep pure helper tests too. Use unit tests for selection/name/status/mapping logic, and provider tests for Alchemy lifecycle semantics.
Build and type checking
Always type-check before committing changes that touch a stack, custom resource, or runtime binding:
bun tsc -b
This runs the workspace build in project-reference mode. CI fails on type errors, so this is non-negotiable. When stale artifacts or dependency drift cause unexplained failures, fall back to a clean rebuild:
bun run build # clean + tsc + build the alchemy package
bun build:clean # full reset: clean . + bun i + build + download env
bun build:clean removes untracked files (preserving .env), reinstalls dependencies, rebuilds, and refreshes environment files. Treat it as a recovery command, not a routine one.
Secrets and config
Do not commit provider tokens, API keys, generated profiles, or state files unless the repo explicitly owns a remote state backend and the file is designed for version control.
Use these rules:
- Prefer provider profiles or CI secrets for deploy credentials.
- Use Alchemy secret resources or provider-native secret resources for runtime secrets.
- Keep non-secret config out of secret resources. Runtime configuration is a named config file in the repo, read through
effect/Config; on Workers, bundle the named configs and let the deploy set only the selector var (ADR-0014). - In darkmatter repos, route durable secrets through the repo's existing SOPS/Himitsu/CI secret path instead of inventing a new
.envconvention. - Document which profile, account, zone, region, or project owns each deploy target.
CI deploys
CI should be deterministic and non-interactive:
- Install dependencies with the repo's locked package manager command.
- Run type checks before deploy.
- Authenticate the provider without a browser prompt.
- Deploy with explicit stack path, stage, and profile.
- Print or capture stack outputs for smoke tests.
- Destroy preview stages on branch/PR cleanup when the workflow owns temporary infrastructure.
Example CI command shape:
bun install --frozen-lockfile
bun tsc -b
bun alchemy deploy ./alchemy.run.ts --stage "$ALCHEMY_STAGE" --profile "$ALCHEMY_PROFILE"
If a deploy failure starts in provider auth, profile lookup, state lock/state persistence, or missing CI secrets, fix that layer before changing resource code.
Preview URLs and webhooks
Public previews are an expected Alchemy workflow:
- PR deploys should use
--stage pr-<number>and post the resulting app URL back to the PR when the repo has GitHub access. - Local development should use
alchemy devwhen a webhook or collaborator needs to reach a public URL backed by local code. - Preview URLs should be derived from stack outputs or provider resources, not guessed from PR numbers.
- Webhook tests should exercise the real external sender when feasible, not only
curl. - Cleanup should destroy preview stages on PR close/merge and retire temporary webhook registrations.
Verification
Always verify the real deployed target when the request concerns deploy behavior:
bun alchemy deploy ./alchemy.run.ts --stage "$STAGE" --profile "$PROFILE"
curl -fsS "$DEPLOYED_URL"
For local dev behavior, run the dev server and hit both local and public/proxied URLs when Alchemy prints both:
bun alchemy dev ./alchemy.run.ts --stage "$STAGE" --profile "$PROFILE"
curl -fsS "$LOCAL_URL"
curl -fsS "$PUBLIC_PREVIEW_URL"
For Effect-native workers/functions, prefer a deployed fixture or smoke route over a purely local assertion. Fresh Workers, Lambda URLs, queues, cron, and DNS often need bounded retries for propagation; use Effect.retry or Effect.repeat with Schedule, not manual polling loops.
Review checklist
- The stack path is explicit where the repo has multiple apps or packages.
- Alchemy is used as the deploy/dev control plane unless the repo has an explicit reason not to.
- Stack name, logical IDs, stage names, and physical names are stable and deterministic.
- Provider and state store choices are intentional for the environment.
- Prod deploys pass explicit
--stageand--profile. alchemy devis configured for local loops that need real cloud resources, public preview URLs, or webhook testing.- Runtime resources use bindings rather than scattered string env vars where Alchemy supports bindings.
- Secrets are not committed, echoed in logs, or encoded into stack outputs.
- CI deploys are non-interactive and verify stack outputs or live endpoints.
- Preview deploys have a destroy path.
- Effect code uses typed errors, Layers, and
Schedulefor retries instead of raw promises, bare SDK calls inside business logic, orDate.now()polling.
Contributing back to alchemy-effect
If a darkmatter repo needs a missing provider feature, the path is usually a PR upstream rather than a local fork. Two conventions matter when you open that PR:
- PR title uses conventional commits (
feat(aws/s3): add bucket lifecycle rules,fix(website): mobile theme metas). - PR description never starts with a
#or##heading — GitHub already renders the title above it. Use###at most, and only when the description genuinely has multiple sections. Lead with a short prose summary or a code snippet that shows the new shape; cut anything that restates what the diff already shows. Never include a "Test plan" or checklist. - Outstanding work belongs in the PR-creation chat, not in the PR body. If something is unfinished or needs manual verification, mark the PR as draft and tell the user what is outstanding.
- Body delivery: write the body to a temp file and pass
--body-filetogh pr create/gh pr edit. Heredoc/--body "$(cat …)"mangles backticks acrossghversions.
If you edit tutorial docs under website/src/content/docs/tutorial/, one concept gets one ## heading, one diff snippet, and one prose paragraph. "Two/three things just happened" + a numbered list is the smell that says split the snippet.
If you edit resource JSDoc, run bun generate:api-reference to refresh website/src/content/docs/providers/{Cloud}/{Resource}.md. The generated markdown is overwritten on every regeneration — never hand-edit it.
reference/provider-implementation.md has the detailed conventions you need before touching anything under packages/alchemy/src/ upstream.
References
reference/upstream-concepts.md— Alchemy v2 andalchemy-effectconcepts applied during app/deploy work.reference/examples.md—alchemy-run/alchemy-effect/examplesdirectories mapped to common deploy scenarios.reference/provider-implementation.md— file system, reconciler doctrine, capability shape, tags, runtime context, test fixtures, JSDoc/docs, and tutorial standards for upstream provider contributions.reference/alchemy-effect/— pinned git submodule ofalchemy-run/alchemy-effectfor current source, examples, docs, and upstream agent instructions.