alchemy-provider
Skill1.8k starsChanged 2 days agoApache-2.0View on GitHub
Bring up a new Alchemy cloud provider (its own `@alchemy.run/<provider>` package under packages/<provider>) end to end — credentials, a distilled SDK, resources, bindings, live tests, and the patch loop that feeds live-test failures back into distilled — or extend an existing provider with a new service, or bring an existing provider up to the bar (gap audit, then waves). Use for "add a <X> provider", "create an alchemy provider for <X>", "port <X> to alchemy", "add <service> to the <X> provider", "bring the <X> provider up to par", or any work that needs a distilled SDK plus Alchemy resources tested against a live API.
We split this skill into the rules it gives an agent
47 rules. About 0 are common, 20 are distinctive, and 27 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈20
Distinctive · estimate
This author's own choices. Worth a look.
27
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 29
Coverage is 1:1 with the API, operation by operation: Every operation in every in-scope service ends up as exactly one of: part of a …
- Distinctive: line 72
When the provider already exists (a PR in progress, or a package on main), start with a gap audit instead of step 0:
- Very specific: line 77
Run every check in review.md against the code as it is, and port scripts/audit-service.ts (step 2) to get per-service operation coverage.
- Distinctive: line 86
Do the distilled work first.
- Distinctive: line 123
Offer periodic status updates: when the run will take more than an hour, and include the index counts in each.
- Distinctive: line 139
When a rebase or pull brings in a newer version of this skill, re-read the whole skill and review.md before the next wave.
- Very specific: line 147
After merging main, type-check the provider package on its own first (cd packages/<provider> && pnpm exec tsrs -b), then the workspace.
- Very specific: line 151
Commit with the repo's hooks.
- Very specific: line 183
Every provider talks to its API through a distilled SDK (@distilled.cloud/<pkg> in submodules/distilled/packages/<pkg>).
- Very specific: line 187
No SDK yet: follow the distilled-sdk skill in the submodule (submodules/distilled/.agents/skills/distilled-sdk/SKILL.md).
- Very specific: line 194
SDK is behind its spec: follow distilled-sdk-update.
- Very specific: line 234
If a skill file is missing at the pinned submodule commit, read it from distilled main: git -C submodules/distilled fetch origin main && …
- Distinctive: line 264
Read operations a user calls at runtime (get an item, list objects, describe a queue's depth) are bindings even when a lifecycle uses them …
- Very specific: line 330
Never hand-edit exports. Run pnpm exports:sync after adding or removing a service directory.
- Very specific: line 373
If providers() wires hundreds of resources, memoize it (let cached.
- Very specific: line 393
Identity changes replace: If a user-chosen name or id is part of the resource's identity, diff returns replace when it changes.
- Distinctive: line 396
Missing is not forbidden: Only a typed not-found tag means "gone".
- Distinctive: line 403
Ownership markers go in labels or tags: When the API has neither, document the fallback marker in the resource JSDoc.
- Very specific: line 416
SQL databases take
migrations: Every resource that hosts a SQL schema applies migrations on deploy through @alchemy.run/sql/Migrations. - Distinctive: line 431
Follow the binding sections of AGENTS.md, then copy the AWS and GCP packages: they are the reference for how bindings reach their host, and …
- Very specific: line 450
Read/Write/ReadWrite services from AGENTS.md on top of the per-operation ones.
- Very specific: line 462
Every binding is a distilled call: the <Op>Http.ts layer wraps <service>.<operation> from @distilled.cloud/<pkg>.
- Very specific: line 518
Never call host.bind(...) from a binding, and never allowlist the provider's own host types.
- Very specific: line 523
Never name another provider's resource type (host.Type === "Cloudflare.Worker").
- Distinctive: line 540
Document any grant broader than the bound resource (a condition on a project-level grant, say) in the binding's JSDoc, with the reason.
- Distinctive: line 622
A Website target: when the runtime is how the cloud serves server-rendered pages (step 7).
- Very specific: line 631
If the cloud has a durable execution engine, the provider ships an Effect workflow engine on it, so alchemy/Workflows code runs there …
- Distinctive: line 633
Follow Effect workflow engines in AGENTS.md: it covers the DX to keep, the two engine shapes, the shared encoding module, and the eight …
- Distinctive: line 648
In-process: when the runtime that starts runs can also host the durable construct (Cloudflare).
- Distinctive: line 651
Host + remote: when runs need a dedicated durable runtime (Lambda durable functions, and most likely Azure Durable Functions).
- Very specific: line 656
If the cloud has no durable engine, or only one that can't run code steps, write out-of-scope with the reason on the engine's index row.
- Distinctive: line 658
Don't build a database-backed engine of your own.
- Distinctive: line 683
Lifecycle runs by default: Create, update, and (where applicable) replace against the real API.
- Very specific: line 700
Assertions can fail: No always-true checks, no lists of acceptable tags, no early return that skips the assertions.
- Distinctive: line 719
Do one-time console work (free trials, free organizations, sign-ups) in the browser with whatever browser-automation skill the harness has, …
- Distinctive: line 729
Stop chasing the long tail after the three-iteration budget from the
- Very specific: line 809
Before choosing a container, check whether the cloud's docs have a hosting page for the framework (Azure Static Web Apps has one for …
- Distinctive: line 841
Every failure the live run exposes is classified before it is fixed, in this order: provider bug > distilled patch > test fix.
- Very specific: line 852
After a patch, regenerate only that package (pnpm generate <pkg> in the submodule) and rerun the test immediately — the test runner reads …
- Very specific: line 858
After a full run, list what is still alive in the test account (pnpm nuke --dry-run --profile testing --include <Provider>.* or the …
- Distinctive: line 865
Run every check in review.md with a fresh reviewer subagent and fix what it finds.
- Very specific: line 903
Don't show a binding running in a host unless a test deploys it that way.
- Distinctive: line 967
every SDK service is cataloged and every operation is classified.
- Very specific: line 968
every in-scope resource has its own passing live test (or is blocked: cost / blocked: skipped and the user has the list).
- Very specific: line 970
every binding operation is a least-privilege binding over distilled, goes through <Provider>Bindings.Bind, reaches foreign hosts, and is …
- Very specific: line 973
no alchemy code makes a hand-written API call (review.md's "Hand-written HTTP" check is clean).
- Very specific: line 975
every runtime ships both forms with InferEnv, its event sources, and tests for each form.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Building an Alchemy provider
A provider is built on a flywheel. Each turn of it makes both the SDK and the provider more correct:
1. distilled SDK ──> 2. alchemy resources ──> 3. live tests
^ │
└──── 4. distilled patches <── wrong error / wrong schemaThe live API is the only authority. A resource is done when its test passes against the real provider, and every mismatch that test surfaced is fixed in distilled rather than worked around in alchemy.
The target is the whole provider. "Create a provider for X" means every resource in every service the distilled SDK covers is either implemented and live-tested, or listed as out of scope with a reason from step 2. A handful of foundation resources is the first wave, never the deliverable. Cost is not a scoping reason: expensive resources are implemented like any other (see step 6 for running their tests). The user narrows scope; the agent does not.
Coverage is 1:1 with the API, operation by operation. Every
operation in every in-scope service ends up as exactly one of: part of a
resource's lifecycle, a binding, an event source, or an internal row
with a reason. A service with resources but no bindings is half done.
AWS is the bar: 2,846 binding services across 205 of its 212 services.
GCP reached 57 of 161 services and Azure 4 of 160; both were reported as
done. Every call goes through distilled. When distilled is missing an
operation, patch distilled (step 1); never work around it in alchemy.
The whole provider also means its non-resource deliverables: a binding for every data-plane operation (step 5), every runtime in both forms, event sources, and a workflow engine when the cloud runs code, framework websites on the cloud's CDN and functions (step 7, when the cloud hosts websites), examples, the docs hub, and generated docs (step 10). They are rows in the same index as resources, with the same statuses. Never write "a later phase" into an agent contract without adding those rows: on Azure, the contract said "bindings are a later phase — do not build them", the phase never came, and 1,193 resources shipped with zero bindings, no runtime, no examples, and docs for 4 services.
AGENTS.md owns the doctrines this skill relies on; read these sections
before writing code and follow them as written:
- Package boundaries — every provider is its own workspace package;
core never imports it, and it reaches core only through
alchemy/*. - Reconciler doctrine — the observe → ensure → sync shape of
reconcile. - Typed Error Doctrine (distilled) — every error alchemy handles is a typed tag in the SDK.
- Speed doctrine: never wait on a hang — bounded retries,
timeouton every test run, the three-iteration budget. - Read/Write/ReadWrite binding convention, Runtime-only methods, and Isolate scope vs request scope — bindings and runtimes.
- Documentation Generation — JSDoc is the only docs source.
- The Resource Factory Process — how to fan the work out to agents.
Those rules existed while the GCP provider was built, and its first review still found the same violations in hundreds of files. Reading the rules is not enough: review.md turns each of them into a check with a command and an expected result. Run that review at every wave boundary and before step 10; a provider with unexplained hits is not done.
Bringing an existing provider up to the bar
When the provider already exists (a PR in progress, or a package on
main), start with a gap audit instead of step 0:
- Merge
origin/maininto the branch and type-check the package (see Branch, checkpoints, and PRs). - Run every check in review.md against the code as it is,
and port
scripts/audit-service.ts(step 2) to get per-service operation coverage. - Rebuild
INDEX.mdfrom the results: one row per missing resource, binding, runtime form, event source, workflow engine, website rework, example, docs page, and JSDoc gap, plus one per review hit that needs a fix (hand-written HTTP,host.bind, a container website). Existing rows keep their status only when review.md passes them. - Do the distilled work first. Missing data-plane modules, credentials providers, and manual specs block the bindings that depend on them.
- Then run the waves from step 4 onward, with the same contracts as a new provider.
Report the audit to the user before the first wave: counts per category and the planned waves.
Orchestration
Fan-out work (the gap audit, the catalog in step 2, implementation waves,
fix rounds in step 8, the review gate in step 9) runs as a workflow
when the harness has one: Claude Code's Workflow tool (load its
workflow-authoring skill before writing the script), Grok's workflow
tool (its create-workflow skill), or the equivalent elsewhere. Run a
workflow only when the user asked for one, since it spends many agents. A workflow runs the whole wave as one
script with bounded parallelism, enforces an agent budget, and journals
every finished agent's result, so a crash costs only the agents still
running. When no workflow tool exists, launch each agent as a detached
top-level thread (t3_thread_launch with workspaceStrategy: "worktree"
in T3 Code), never a delegate_task subagent thread; the coordinator reads
each thread's result with t3_thread_read. Either way,
each agent gets the task contract from What every agent task prompt must
include in AGENTS.md, owns one distilled service, and returns the
structured result the coordinator turns into index statuses.
Coordinator duties during a long run:
- Size agents to finish within an hour. A service with more than about 25 resources runs as a chain of agents over resource batches. Agents that run for hours hit the model's idle timeout and lose their context.
- Read the workflow state, not only file activity. Each status check
counts agents by state and reads the error of every failed or cancelled
one. A model-API outage (
auth_unavailable, idle timeouts) looks like a quiet period from the file system; on Azure it went unnoticed for three hours. Relaunch only the rows stillmissing, with an assess-first contract, once the cause is gone. - Offer periodic status updates when the run will take more than an hour, and include the index counts in each.
Branch, checkpoints, and PRs
- Work in a fresh worktree on a branch from
origin/main, withsubmodules/distilledat the commit alchemy pins plus your distilled branch. Never start from whatever branch the worktree happens to be on. - Open draft PRs in alchemy and distilled after the first wave that passes live, and push a checkpoint at every wave boundary. The description carries the current index counts.
- Each checkpoint: rebase onto
origin/main(distilled onto the pinmainuses), type-check, push, and confirm the PR's checks actually ran. A PR with merge conflicts runs nopull_requestworkflows, so a stale green check means nothing. Dependency bumps onmain(Effect, Node types) break new code; catch them at the checkpoint. - When a rebase or pull brings in a newer version of this skill, re-read the whole skill and review.md before the next wave. Add an index row for every new deliverable, then build those rows and run review.md with a fresh reviewer.
pnpm exec tsrs -breads distilled's built declarations (lib/), while the test runner reads itssrc/. After distilled patches or a pin bump, rebuild that package before the type check, or new error types show up as false type errors.- After merging
main, type-check the provider package on its own first (cd packages/<provider> && pnpm exec tsrs -b), then the workspace. A newer distilled pin is the usual break: a shape that became open ([key: string]: unknown) or a renamed error tag. - Commit with the repo's hooks. If signing or a hook hangs, ask the user;
if they approve skipping it, run
pnpm exec oxfmtyourself and say the commits are unsigned.
Step 0 — get credentials first
Always ask the user for credentials for the upstream provider before anything else, even when the request does not mention testing. Live tests are the whole point of the loop, and a provider built without them is unverified. Ask in one message, and name exactly what is needed:
- the auth method the provider supports (API token, service account key, OAuth app, access key pair) and the minimum scopes or roles the test resources need;
- a dedicated test account, project, or organization — never a production one, because tests create and nuke resources;
- account identifiers the API needs (org id, project id, default region);
- known limits of that account (plan tier, quotas, features that need an upgrade), so gated tests can be planned up front.
Store what the user gives you in an Alchemy profile, never in the repo:
alchemy profile edit --profile testing --add <Provider> (use the profile
name the user asks for). Tests select it with --profile <name>. Never
print a secret, write it to a tracked file, or put it in a PR, commit, or
log. If the user has no credentials, give them the exact steps to create
them (console path or CLI commands, roles to grant), offer to do it in the
browser if they allow it, and keep working on the catalog and code
meanwhile. Until credentials arrive, mark every live test as blocked in
your report; do not claim anything is tested.
Step 1 — the distilled SDK
Every provider talks to its API through a distilled SDK
(@distilled.cloud/<pkg> in submodules/distilled/packages/<pkg>). Alchemy
never calls the API with a hand-written HTTP client.
- No SDK yet: follow the
distilled-sdkskill in the submodule (submodules/distilled/.agents/skills/distilled-sdk/SKILL.md). It covers sourcing the spec, the mirror, convert/generate, the README, and the distilled PR. - SDK exists but is wrong (missing typed error, nullable field, wrong
response schema, unmarked secret): follow the
distilled-sdk-patchskill (submodules/distilled/.agents/skills/distilled-sdk-patch/SKILL.md). - SDK is behind its spec: follow
distilled-sdk-update.
That includes the data plane. Bindings call the cloud's data APIs
(put a blob, send a message, read a secret), and those go through
distilled exactly like the management API that creates the resources.
AWS is the model: sqs.sendMessage, s3.putObject and
dynamodb.getItem are distilled operations, and AWS bindings hold no
HTTP code. Check data-plane coverage when you check the SDK. If the SDK
covers only the management API, extend it before writing a binding:
- The spec has a data-plane model (Azure's
specification/<service>/data-plane/, GCP's data APIs in discovery docs): extend the converter to include it, as its own service module (storage-queue,keyvault-secrets). - The API has no published spec (Kudu zip deploy, a CLI-only
endpoint): write the model by hand in the package's
manual-specs/. - The protocol isn't HTTP-shaped (XML bodies, SAS signing, a token audience per data plane): add it to the distilled protocol, not to alchemy.
- Credentials from the runtime's identity endpoint (managed identity,
metadata server, container credentials) are a distilled credentials
provider, like AWS's
fromContainerMetadata. A binding never fetches its own token.
On Azure, the converter skips data-plane/ (scripts/convert.ts), so
the agent hand-wrote XML request builders for Storage queues and blobs,
Key Vault secrets, Service Bus, Event Hubs, a static-website uploader, and
a managed-identity token cache: 22 raw request sites, with no typed
errors and no tests against a spec. Don't repeat it. Stop and extend
distilled.
Raw HttpClient calls are allowed only for:
- URLs the API hands back to you to use as-is (a presigned or SAS upload URL, a signed download URL);
- the user's own deployed endpoints (calling a Worker, Function, or service the stack deployed).
Each one carries a comment saying which case it is.
If a skill file is missing at the pinned submodule commit, read it from
distilled main: git -C submodules/distilled fetch origin main && git -C
submodules/distilled show origin/main:.agents/skills/<skill>/SKILL.md.
Distilled changes land as their own PR in alchemy-run/distilled. After
it merges, move the submodule pin in alchemy to the merged commit
(git -C submodules/distilled checkout <sha> then commit the gitlink) and
run pnpm install so the workspace links the package.
Step 2 — catalog and scope
Catalog every service module in the SDK
(ls submodules/distilled/packages/<pkg>/src/services) before writing
resources; for a large SDK, fan the catalog out as a workflow (see Orchestration). Each service gets
processes/<Provider>/catalog/<service>.md in that section's format:
resources, props with replacement rules, attributes,
lifecycle-to-operation mapping, bindings, testability, and priority.
processes/ is gitignored, so the catalog is the local order book; the
committed record of scope is the overview page below.
Each catalog file also lists every operation in the service module,
classified the way scripts/audit-service.ts classifies AWS:
| Category | Meaning | Becomes |
resource-lifecycle | create/get/update/delete/list of the resource itself | a resource's reconcile/read/delete/list |
binding | data-plane work on or inside a resource (put an object, send a message, query a table, read a secret, invoke a function) | a Binding.Service + <Op>Http.ts |
event-source | the resource triggers runtimes (notifications, streams, subscriptions) | an event source |
internal | not useful from user code (long-running-operation polling, deprecated aliases) | nothing, with a reason |
Read operations a user calls at runtime (get an item, list objects,
describe a queue's depth) are bindings even when a lifecycle uses them
too. Port scripts/audit-service.ts to the provider before the first
wave (it reads the distilled module and the package and prints the gaps),
and run it per service at every wave boundary.
processes/<Provider>/catalog/INDEX.md has one row per resource and one per binding with a
status of missing, implemented, tested, blocked (with the exact
error), or out-of-scope (with the reason). Build waves from it and repeat
until no row is missing or implemented. Every progress report and the
final report quote its counts per status; "done" while any in-scope row is
missing is a false claim.
The first line of INDEX.md records the skill version it was built
against: Skill: <sha>, from git log -1 --format=%H --
.agents/skills/alchemy-provider. Update it after re-reading a newer skill.
Decide scope per resource and record the out-of-scope list in the provider's website overview under an "Out of scope" section:
- In scope: infrastructure the user configures and owns (databases, buckets, queues, networks, keys, functions, DNS).
- Out of scope (the only valid reasons): end-user data APIs (mail, calendar, documents, videos), APIs that need end-user OAuth scopes, deprecated or retired APIs, billing/subscription objects, closed-beta endpoints, and duplicates of another service.
Fix public names now; renaming after release is a breaking change.
Service namespaces are PascalCase (AccessContextManager), and resource
names are correct English singulars (Index, not Indexe; Process, not
Processe). Audit generated names by eye before the first PR.
Step 3 — the provider package
A provider is its own workspace package, @alchemy.run/<provider> in
packages/<provider>, following Package boundaries in AGENTS.md.
Copy the closest small package: packages/axiom (token auth, flat
resources) or packages/hetzner (labels, list, nuke ordering, bindings,
a hosted runtime, websites). A package holds:
| File | Purpose |
package.json | name, shared alchemy version, publish metadata, exports/publishConfig (generated, below), alchemy.contributes, dependencies |
tsconfig.json | composite project; references the distilled package, distilled/core, and ../alchemy |
scripts/build.ts | copied verbatim from a sibling package |
src/Contributions.ts | Contributions.make({ name, auth, … }) — what the CLI, the Alchemist API, and the test harness load without the user's stack |
src/AuthProvider.ts | makeStoredAuthProvider fields, CI env vars, <Provider>Auth layer |
src/Credentials.ts | fromAuthProvider() = lazyCredentials<Resolved, ConfigError>()(...) → the distilled Credentials layer |
src/Providers.ts | ProviderCollection + providers() layer (provider layers, credentials, FetchHttpClient, auth, profile and credential stores) |
src/<Provider>Bindings.ts | how bindings reach the cloud from any host (step 5) |
src/globals.d.ts | import type {} from "alchemy/Phase"; so globalThis.__ALCHEMY_RUNTIME__ type-checks |
src/index.ts | re-exports every resource, binding, and layer; export * as <Service> from "./<Service>/index.ts" |
src/Labels.ts / tags helper | ownership labels, if the API supports labels or tags |
src/Bootstrap.ts | process entry for a hosted runtime, if the cloud runs code (source-only export) |
README.md | npm readme: short intro, install line, one complete type-checked example, absolute https://alchemy.run links |
Contributions.ts is imported on every CLI start, so it holds metadata and
Contributions.lazy(() => import(...), pick) fields only. Contribute auth
always; add stateStores, cli, testServices, and devEnv when the
provider has them (packages/aws/src/Contributions.ts uses all of them).
Declare it in package.json:
"alchemy": { "contributes": ["@alchemy.run/<provider>/Contributions"] }Never hand-edit exports. Run pnpm exports:sync after adding or
removing a service directory; CI runs pnpm exports:check. Modules other
packages must not import are named internal.ts or live under
internal/. Dependencies: @distilled.cloud/<pkg> and
@distilled.cloud/core are dependencies; alchemy and effect are
peer + dev dependencies; another @alchemy.run/* package is a dependency
only when every user needs it, otherwise an optional peer.
Register the package everywhere the repo enumerates providers. Find the
full list by grepping for an existing package (git grep -il hetzner --
':!packages/hetzner' ':!submodules' ':!examples'); at the time of
writing it is:
package.json— thebuild:pkgfilter list and a root@alchemy.run/<provider>devDependency (the CLI findsContributionsthrough the project's dependencies);tsconfig.json— project references to the package and its distilled package;packages/integration-tests/package.jsonandtsconfig.json— the package, its distilled package, and the project reference;.github/workflows/pkg.yml— the@alchemy.run[Collapsed]group;.gitignore—!packages/<provider>/src/globals.d.ts;stacks/nuke.ts—<Provider>.providers();scripts/with-fake-profiles.sh— fake CI credentials;scripts/generate-api-reference.tsandscripts/fix-api-jsdocs.ts— the package'ssrcroot and provider prefix;packages/integration-tests/test/Bundle/ProviderBootstraps.test.ts— everyBootstrapthe package ships;website/astro.config.ts,website/src/components/ProviderDirectory.astro,website/scripts/generate-llms-txt.ts, andwebsite/src/content/docs/<provider>/(index.mdx,setup.mdx) — sidebar, directory card, llms.txt, overview, and setup page.
Core (packages/alchemy) never changes to add a provider. If a provider
seems to need a core change (a new auth registry entry, a CLI command, a
state store), it is a Contributions field instead.
Configuration such as region, project, or account comes from the
credential/profile or from a layer the user provides (the AWS
AWS.Region(...) pattern). Do not invent environment variables for it;
env vars exist only for CI credentials and for values the runtime platform
itself sets.
If providers() wires hundreds of resources, memoize it
(let cached; export const providers = () => (cached ??= make())) and keep
nested Layer.mergeAll groups of about 90; an unmemoized collection
rebuilds every layer per call and runs the test process out of memory.
pnpm lint:providers fails when a providers() layer leaks an unknown
requirement.
Step 4 — resources
Implement each resource per Reconciler doctrine, co-locating the
contract and provider in packages/<provider>/src/<Service>/<Resource>.ts. These
are the mistakes that most often slip through review (each has a check in
review.md):
- Block until ready.
reconcilereturns only after the API reports the resource usable: poll the long-running operation to completion and then read the ready state. Use one shared operation waiter per provider with a per-resource time budget that matches reality (minutes for databases and clusters). Post-ready propagation delay is acceptable; returning early is not. - Identity changes replace. If a user-chosen name or id is part of the
resource's identity,
diffreturnsreplacewhen it changes; otherwise the old resource leaks. - Missing is not forbidden. Only a typed not-found tag means "gone". A permission error treated as not-found drops the row from state and leaks the resource. If the API returns 403 for missing resources, patch distilled with a specific tag for that case.
- Lists fail loudly and paginate. A
listthat swallows errors reports "nothing here" to nuke and hides leaks. Use the SDK's paginated.items(...)stream. - Ownership markers go in labels or tags. When the API has neither, document the fallback marker in the resource JSDoc; never put markers in fields users see or that change behaviour.
- Shared helpers live once per provider (operation waiter, not-found helper, label diffing). Copies per service drift apart.
- Enable what the account needs. Clouds that require a service to be
switched on per account (Azure resource provider registration, GCP API
enablement) get an idempotent enable step inside
reconcile, so a fresh account deploys without manual setup. - Clean up after a failed create.
readanddeletework whenoldsor parts ofoutputare missing, so the next run's openingdestroyremoves whatever a crashed run left behind. Prove it once by interrupting a create. - SQL databases take
migrations. Every resource that hosts a SQL schema applies migrations on deploy through@alchemy.run/sql/Migrations; the provider only writes the cloud-specific call, built on its executor helpers (withPgExecutor,makeStatementExecutor,splitSqlScript). Prefer the cloud's HTTPS data API, then a direct or connector connection, then an in-network runner, and take credentials from the platform before asking formigrationsUser/migrationsPassword. Follow SQL database migrations inAGENTS.md; a SQL database shipped without migrations is incomplete. - Implement
listfiltered to Alchemy-owned resources, and declarenuke: { dependsOn: [...] }where delete order matters, soalchemy unsafe nukecan clean the test account.
Step 5 — bindings and runtimes
Follow the binding sections of AGENTS.md, then copy the AWS and GCP
packages: they are the reference for how bindings reach their host, and
most questions are answered by reading the closest existing binding. The
pattern was set by these PRs; read their descriptions when a case isn't
obvious from the code:
| PR | What it settled |
| #2260 | bindings state what they need; a <P>Bindings module decides how the host gets it (packages/aws/src/AWSBindings.ts) |
| #2347 | Bind / withCredentials effectful constructors, identity from real resources, __ALCHEMY_HOST_IDENTITY__ bundle fold (packages/gcp/src/GCPBindings.ts) |
| #2267 | deploy-only services read behind the runtime guard; *Http bindings on local resources fail with a typed error (packages/cloudflare/src/HttpBindingDev.ts) |
| #2244, #2315 | no branching on another provider's host type; reach the host through RuntimeContext and core host services (alchemy/WebhookHost) |
Cover every binding operation in the catalog. Follow the AWS
shape: one Binding.Service per operation (SQS.SendMessage,
DynamoDB.GetItem), each with an <Op>Http.ts layer built on the
distilled operation, which injects the bound resource's identifier and
grants only that operation on that resource. Where the API has clear
access levels (object storage, key-value stores), also ship the
Read/Write/ReadWrite services from AGENTS.md on top of the per-operation ones.
Least privilege depends on the cloud's permission model:
- Per-action permissions (AWS IAM): grant the operation's actions on
the resource ARN, as
scripts/audit-service.tschecks. - Role-based (GCP IAM, Azure RBAC): grant the smallest predefined role that covers the operation, on the resource, never the project or subscription. If the smallest predefined role covers much more than the operation, create a custom role with exactly the permissions or data actions it needs, and reuse it across hosts.
Every binding is a distilled call: the <Op>Http.ts layer wraps
<service>.<operation> from @distilled.cloud/<pkg>. If the operation
isn't in distilled (the data plane is missing, or the operation isn't
modelled), patch distilled first (step 1).
Declare each capability as an interface plus factory:
export interface SendMessage extends Binding.Service<
SendMessage,
"AWS.SQS.SendMessage",
(queue: Queue) => Effect.Effect<(request: SendMessageRequest) => Effect.Effect<…>>
> {}
export const SendMessage = Binding.Service<SendMessage>("AWS.SQS.SendMessage");Reach any host. A binding works on the provider's own compute and on
every other cloud's: a Lambda can bind a GCP topic, a Cloudflare Worker
an SQS queue. src/<Provider>Bindings.ts owns the decision and exports:
HostBinding— what a binding attaches: grants (IAM statements, role bindings, role assignments) plusenv.Bind— yields a tagged-templatebind. On a host that runs as the cloud's own identity (runsAsRole,runsAsServiceAccount; an Azure managed identity) the grants go on that identity. Any other host gets an identity built from the provider's own resources (IAMUser+AccessKey+Roleon AWS,ServiceAccount+ServiceAccountKeyon GCP), the grants go on it, and its key goes into the host's env throughCurrentRuntimeContext. With no host (a script, plan-timeexecute) it does nothing. In a host bundle it is a no-op, so the identity resources drop out.withCredentials(operation)— resolves a distilled operation once when the layer is built: the host's ambient identity, the bound key read back fromRuntimeContext, or the deployer's credentials.<identity>HostBundle— the rolldown option the provider's own host bundlers apply (Lambda/FunctionBundle.ts,CloudFunctions/FunctionSource.ts, theImageSource.tsfiles). It sets__ALCHEMY_HOST_IDENTITY__(declared in core'sPhase.ts) so the created-key path drops out of native bundles.
A binding layer then reads:
Effect.gen(function* () {
const publish = yield* GCPBindings.withCredentials(pubsub.publishProjectsTopics);
const bind = yield* GCPBindings.Bind;
return Effect.fn(function* (topic: Topic) {
if (!globalThis.__ALCHEMY_RUNTIME__) {
yield* bind`GCP.PubSub.WriteTopic(${topic})`({ iam: [/* scoped to topic */] });
}
return (message) => publish({ topic: …, body: … });
});
});Rules the reference code follows:
- Never call
host.bind(...)from a binding, and never allowlist the provider's own host types; both lose foreign hosts. The exceptions are host-only attachments that make no sense elsewhere, and they get their own helper (AWSBindings.bindVpc, the Cloud SQL socket mount inGCP.SQL.Connect). - Never name another provider's resource type (
host.Type === "Cloudflare.Worker"). Write env throughRuntimeContext(runtime.setat deploy,runtime.getat runtime; secrets stayRedacted), and use core host services such asWebhookHostfor anything else a host must provide. - Services that only exist at deploy (
<P>Environment.current, the account or project id for a grant) are read inside the!globalThis.__ALCHEMY_RUNTIME__block. Read at layer build, they crash a foreign host at init. - Per-service grant helpers are constructors too (
GrantOnQueueinpackages/aws/src/SQS/BindingHttp.ts), and shared scaffolding lives in an unexportedBindingHttp.tsso each<Op>Http.tsis a one-lineLayer.effect(Cap, make…({ … })). - A provider with local emulation fails an HTTP binding to a locally
emulated resource with a typed error naming the native binding and
Alchemy.remote()(HttpBindingUnsupportedInDev).
Document any grant broader than the bound resource (a condition on a project-level grant, say) in the binding's JSDoc, with the reason.
Runtimes
Find every runtime in the catalog: a resource that runs the user's
code (functions, containers, services, workers, jobs, VMs that run a
program). Mark each one runtime in INDEX.md. Each ships in two
forms from the same resource:
Effectful — a class built with core's Platform (alchemy/Platform).
main: import.meta.url makes the file both the bundle entry and the
program. Construction binds resources, and the returned handlers run per
request:
export default class Api extends GCP.Function<Api>()(
"Api",
{ main: import.meta.url },
Effect.gen(function* () {
const db = yield* GCP.Firestore.ReadWriteDatabase(Links);
return {
fetch: Effect.gen(function* () {
const doc = yield* db.get("links/home");
return yield* HttpServerResponse.json(doc?.fields ?? {});
}).pipe(Effect.orDie),
};
}).pipe(Effect.provide(GCP.Firestore.ReadWriteDatabaseHttp)),
) {}Async — the same resource called as a plain function, with main
pointing at an ordinary handler file and no Effect. Alchemy bundles that
file as-is, with no Effect runtime in it. The env prop takes resources,
Config values, and literals, and <Provider>.InferEnv<typeof Fn>
types the handler's env argument:
// alchemy.run.ts
export const Ping = Railway.Function("Ping", {
project: Site,
main: "./src/ping.ts",
env: { DATABASE_URL: db.connectionUri },
});
export type PingEnv = Railway.InferEnv<typeof Ping>;// src/ping.ts
import type { PingEnv } from "../alchemy.run.ts";
export default {
async fetch(_request: Request, env: PingEnv) {
return new Response(env.DATABASE_URL ? "ok" : "missing");
},
};Copy both forms from the runtime closest to yours:
| Runtime | File | Notes |
| Cloudflare Worker | packages/cloudflare/src/Workers/Worker.ts, InferEnv.ts | the full reference: env entries become native bindings |
| Railway Function | packages/railway/src/Function.ts, InferEnv.ts | bindings are env vars, so InferEnv maps to string |
| GCP Cloud Run, Hetzner Service | packages/gcp/src/Run/Service.ts, packages/hetzner/src/Service.ts | Platform over a container or process |
| AWS Lambda | packages/aws/src/Lambda/Function.ts | async form exists but has no InferEnv; don't copy that gap |
Every runtime needs:
- Both forms, each with its own JSDoc section (
### Effect …and### Async …) showing the stack declaration and the handler file. InferEnvexported from the package, so async users get a typedenvwithout writing the type by hand.- The
HostBindingcontract on the resource, so effectful bindings from this and other providers can attach grants and env. - The host bundle option (
<identity>HostBundle) on the effectful bundle, and a source-onlyBootstrapexport for the process entry (packages/hetzner/src/Bootstrap.ts,packages/aws/src/Bootstrap/). - Event sources for what the cloud triggers it with (queues, topics,
storage events, schedules), shaped like AWS
SQS.QueueEventSourceand Cloudflare cron. Their contract lives in the source service and each runtime provides an implementation layer. - A Website target when the runtime is how the cloud serves server-rendered pages (step 7).
- Tests for both forms: a fixture per form under
test/<Provider>/<Service>/fixtures/(effect-worker.tsandasync-worker.tsintest/Cloudflare/Images/are the pattern), each deployed and called over HTTP.
Workflow engine
If the cloud has a durable execution engine, the provider ships an Effect
workflow engine on it, so alchemy/Workflows code runs there unchanged.
Follow Effect workflow engines in AGENTS.md: it covers the DX to
keep, the two engine shapes, the shared encoding module, and the eight
conformance cases.
| Cloud | Engine | Status |
| Cloudflare | Cloudflare.Workflows.NativeEngine (Cloudflare Workflows), DurableObjectEngine | done, in-process |
| AWS | AWS.Lambda.WorkflowFunction + DurableFunctionEngine (Lambda durable functions) | done, host + remote |
| Azure | Durable Functions on the Durable Task Scheduler (Azure.DurableTask.Scheduler exists) | missing |
| GCP | no code-first durable runtime; Cloud Workflows is a YAML orchestrator | decide and record in the catalog before building |
Research the cloud's engine in step 2 and record it in the catalog: its durable step, durable sleep, external-event or callback primitive, how a run is interrupted, and its limits. Then decide the shape:
- In-process when the runtime that starts runs can also host the durable construct (Cloudflare). The engine is a Layer provided next to the workflow Layers.
- Host + remote when runs need a dedicated durable runtime (Lambda
durable functions, and most likely Azure Durable Functions). Ship a
<Runtime>.WorkflowFunction-style host that lists its workflows, and a<Runtime>Engine(...hosts)Layer that routes to it and grants access.
If the cloud has no durable engine, or only one that can't run code
steps, write out-of-scope with the reason on the engine's index row.
Don't build a database-backed engine of your own.
The engine counts as a runtime: it gets its own index row, the
conformance suite, an examples/<provider>-effect-workflows example, and
a compute/effect-workflows.mdx page in the docs hub.
Step 6 — live tests
Tests live in packages/integration-tests/test/<Provider>/ (paths below
are relative to packages/integration-tests). Write them per the test
sections of AGENTS.md (test.provider,
stack.destroy() at start and end, deterministic names, out-of-band
verification through distilled, typed wait-until-gone). Run with the
profile from step 0:
pnpm test:integration test/<Provider>/<Service>/<Resource>.test.ts --profile testing --retry 0What a test must prove:
- Every resource has its own test file
(
test/<Provider>/<Service>/<Resource>.test.ts). Deploying a resource as a dependency inside another resource's test does not count, because its own update, replace, and delete paths never run. - Lifecycle runs by default. Create, update, and (where applicable) replace against the real API. Slow lifecycles are gated behind one provider-wide flag with a realistic timeout; entitlement-bound ones keep an ungated probe that asserts the one specific typed tag the account returns.
- Bindings run inside a deployed host (a fixture Worker, Function, or
container calling the binding over HTTP) so the IAM grant or native
binding is exercised with the host's identity, not the deployer's. Test
both paths of
Bind: the provider's own host, and a foreign host using the created key intest/<Provider>/ExternalHost/CloudflareWorker.test.ts(copytest/GCP/ExternalHost/). If the provider hosts code, also add its host to the other providers' external-host suites (test/Cloudflare/ExternalHost/Lambda.test.tsis the pattern). - Every binding has a test. One
describe("<BindingName>")per binding, all driving the same deployed fixture host (test/AWS/DynamoDB/Bindings.test.ts+handler.tsis the pattern). A binding no test calls from a host is not done. - Assertions can fail. No always-true checks, no lists of acceptable
tags, no early
returnthat skips the assertions.
Expensive resources (dedicated clusters, large VMs or databases,
reserved capacity, anything billed per hour at a noticeable rate) are
implemented and get full tests, but you do not run those tests on your own.
Gate them behind the provider-wide slow flag, mark them blocked: cost in
the index, and list them for the user with an estimate of what one run
costs. Run them only after the user says so.
Scarce account quotas (networks, clusters, IP addresses) are shared with a
semaphore in a test helper, or moved to less-used regions. The suite still
runs in one process: pnpm test:integration test/<Provider> --profile testing.
Unblock account prerequisites in the dedicated test account yourself as
soon as a test hits them. Request quota increases and register preview
features through the provider's API (anything that raises spend needs the
user's go-ahead from step 0 or step 6's cost rule),
and put any setup a fresh account would need again into the test helper.
Do one-time console work (free trials, free organizations, sign-ups) in the
browser with whatever browser-automation skill the harness has, and ask the user only to sign in.
A row stays blocked only when it needs payment, a vendor's approval,
hardware, or is skipped under the long-tail rule below; report each one
with the exact request that would unblock it.
Failure-path tests assert the typed error and finish in minutes. Give them a short timeout and never let them wait out a slow provisioning or delete; a single test case that runs for an hour is a bug in the test.
Stop chasing the long tail after the three-iteration budget from the
Speed doctrine in AGENTS.md, or as soon as the user says so. When the
remaining failures are slow provisioning, platform behavior, or test
infrastructure the cloud needs (a reachable endpoint, a custom image),
let the current run finish, then skip-gate each failing case with its
exact error, mark the row blocked: skipped in the index, and list those
rows in the next report so the user can reopen any. When the user sets a
deadline, cap test fixing at half of it and spend the rest on the other
deliverables (bindings, runtime, websites, examples, docs, PR). On Azure,
fix rounds on one-hour tests kept running after the user asked to skip
them, until the user set a one-hour deadline to wrap up.
Step 7 — framework websites
Skip this step when the provider can't host a website. Most
providers can't: observability, payments, databases, and auth (Axiom,
Stripe, Turso, PlanetScale) have no web hosting, so they ship no
Website namespace and get no "N/A" rows in the index. This step is
for providers that serve HTTP or static files.
Those providers ship <Provider>.Website.<Framework> composites, so a
user deploys an Astro or Next.js app with one call:
const site = yield* AWS.Website.Astro("Site", { rootDir: "./web" });Use the cloud's web hosting, not a container
A website deploys onto the infrastructure the cloud built for websites: static assets on a CDN, and server rendering on the cloud's serverless functions behind that same CDN. Putting the whole build in a container or an always-on VM, with no CDN, is the lazy default. It is not done, even though it works for every framework on the first try.
Azure's first pass did exactly that. All 14 frameworks, the static-only
ones included, became a Docker image on Container Apps. A follow-up moved
static builds to a Storage static website and server builds to an
always-on App Service plan, with Front Door optional. That is still a
VM instead of functions, and no CDN by default. GCP's composites still
deploy a Node image to Cloud Run for every framework, StaticSite
included. Copy neither.
Pick the hosting per cloud before writing code, and record the choice and its reason in the catalog:
| Kind of provider | Static assets | Server rendering | Reference |
| Hyperscaler with a CDN and functions | object storage behind the CDN | serverless functions as the CDN's dynamic origin | packages/aws/src/Website/ (S3 + CloudFront + Lambda, SsrSite.ts) |
| Edge platform | the platform's asset hosting | the platform's functions | packages/cloudflare/src/Website/ (Workers + static assets) |
| Platform with its own function runtime | the platform's asset hosting | its functions, through a framework target | packages/neon/src/Website/ (frontend-frameworks/<framework>/neon.ts) |
| Containers or VMs only | served by the same Node server, behind the platform's CDN when it has one | the Node server | packages/fly/src/Website/, packages/hetzner/src/Website/, packages/railway/src/Website/ (Cdn.ts) |
What that means for the clouds still to be built:
- Azure: Static Web Apps, or Blob static website behind Front Door, with server rendering on Azure Functions (Flex Consumption). Not Container Apps, not an App Service plan.
- GCP: a Cloud Storage bucket behind Cloud CDN on an external HTTPS load balancer, with Cloud Run or Cloud Functions as the serverless origin for server rendering. Assets-only sites never run a container.
Rules:
- An assets-only build (Vite, StaticSite, SPA output) never runs compute.
- The CDN is on by default, not behind a
cdn: trueflag. Assets are uploaded with content hashes and cache headers, and a deploy invalidates or purges what changed (AssetDeployment.ts+CloudFront.Invalidationon AWS). - Server rendering runs on functions that scale to zero. A container or VM is acceptable only when the cloud has no function runtime, or when a framework needs a long-lived process. Document that framework's reason in its JSDoc.
- Functions need a framework target: a new
packages/frontend-frameworks/src/<framework>/<provider>.tsnext toaws.ts,cloudflare.ts, andneon.ts, producing a handler for the cloud's function runtime. Only container/VM providers use the Node target (@alchemy.run/frontend-frameworks/<framework>/node) through core'salchemy/Website/Server. - Before choosing a container, check whether the cloud's docs have a hosting page for the framework (Azure Static Web Apps has one for Next.js, Nuxt, Astro, and SvelteKit). If they do, use what it uses.
Layout
Ship the same framework set as the existing providers (ls
packages/aws/src/Website and packages/hetzner/src/Website: Astro,
Foldkit, Nextjs, Nuxt, Octane, ReactRouter, SolidStart, StaticSite,
SvelteKit, TanStackStart, Vinext, Vite, Vocs, Waku):
FrameworkSite.tsownsmakeFrameworkSite, which builds through the framework integration (@alchemy.run/frontend-frameworks/<framework>) and deploys the output onto the hosting above. Its shared props (rootDir,memo,dev,env,assets,domain,tags) keep the names and meaning the sibling providers use.- One small file per framework that only sets the framework specifier, the deploy target, and the framework's own option block.
StaticSite.tsfor assets-only sites, andindex.tsexported from the provider barrel asexport * as Website from "./Website/index.ts".
Each framework gets a live
packages/integration-tests/test/<Provider>/Website/<Framework>.test.ts
that asserts the response comes through the CDN (a cache header, or the
CDN hostname) and that a server-rendered route hits the function; a
<Framework>.local.test.ts for alchemy dev; and a shared
PropSurface.test.ts. It also gets an examples/<provider>-website-<framework>
example listed in scripts/test-examples.ts, and a page under
website/src/content/docs/<provider>/frontend/.
Step 8 — close the loop
Every failure the live run exposes is classified before it is fixed, in this order: provider bug > distilled patch > test fix.
| Symptom | Fix |
Unknown<Pkg>Error, or a status/message you want to branch on | typed error patch (distilled-sdk-patch) |
| decode failure, field null or missing on the wire | nullable/optional patch |
| secret returned as a plain string | sensitive patch |
| resource leaks, returns before ready, wrong replace | provider fix in alchemy |
| test asserts something the API does not promise | test fix |
After a patch, regenerate only that package (pnpm generate <pkg> in the
submodule) and rerun the test immediately — the test runner reads distilled
from src/, so there is nothing to build. Collect the patches, prove each
with pnpm patches:audit, and ship them as a distilled PR, then bump the
pin (step 1).
After a full run, list what is still alive in the test account
(pnpm nuke --dry-run --profile testing --include <Provider>.* or the
provider's list calls). A green test that
leaves resources behind is a provider bug.
Step 9 — review gate
Run every check in review.md with a fresh reviewer subagent and fix what it finds. Report the result table to the user with the counts, so they can see what was checked without re-reviewing by hand.
Step 10 — docs, examples, and the PR
- JSDoc on every resource, prop, attribute, and binding, to the bar in
docs.md: runtimes as complete as
AWS.Lambda.Function, fat-head resources as complete as S3 Bucket or R2 Bucket, and a creation example plus replacement cautions for the long tail. Then runpnpm docs:check-jsdocandpnpm docs:gen. - The hand-written hub at
website/src/content/docs/<provider>/, built to docs.md: the AWS hub's structure (Overview, Setup, a five-part tutorial, sections by concern), code taken from tested examples, and the voice rules there. A hub of loose "guides" that walk the examples is not done; GCP's had to be rebuilt. - The example set described under Examples below.
- Type-check with
pnpm exec tsrs -b, runpnpm exports:checkandpnpm lint:providers, and format withpnpm exec oxfmt. Open the alchemy PR per Pull Request Conventions, linking the distilled PRs it depends on. The description shows the provider's DX, not a list of what was built:
- One or two lines on what the package is, then a few bullets of counts (services, resources, live test files, gated tests).
- A
###section for every runtime (each form), the event sources, and the websites. - A
###section for each fat-head resource: the 10–20 that most apps on this cloud use (resource groups or projects, identity and roles, object storage, the main SQL and document databases, queues and topics, secrets, the container registry, networking and DNS, logs). Pick them from the cloud's own quickstarts, not from the catalog's order. Group a resource with the ones it's always used with (a namespace and its queue) and its bindings. - Each section is one or two sentences, then one snippet copied from that resource's JSDoc example, so the code is already checked.
- One closing line names the long tail and points to the generated reference. Don't list it.
Don't show a binding running in a host unless a test deploys it that way. On Azure, the description was going to show bindings inside
Azure.Functions.Function, which isn't a registered binding host, so those bindings did nothing.
Examples
Examples are how users learn the provider, and the docs hub copies its
code from them. Ship a comprehensive set, not one or two. Azure's first
pass shipped azure-storage (a Stack with no runtime) and one website;
AWS, Cloudflare, Fly, and Hetzner ship one example per framework plus one
per workload.
Every web framework gets its own example, for every provider that
hosts websites: examples/<provider>-website-<framework>, one per
<Provider>.Website.<Framework> composite (Astro, Foldkit, Nextjs, Nuxt,
Octane, ReactRouter, SolidStart, SvelteKit, TanStackStart, Vinext, Vite,
Vocs, Waku, plus <provider>-static-site). ls examples | grep
hetzner-website- is the complete set to match.
Then one example per workload the cloud supports. Go down this list and add an example for every row the provider has, and skip the rest:
| Workload | Shows | AWS / GCP / Cloudflare reference |
| Serverless function, effectful | an HTTP handler with a binding to storage or a database | aws-lambda, gcp-cloud-function, cloudflare-worker |
| Serverless function, async | the same with a plain handler and InferEnv | cloudflare-worker-async |
| HTTP API | Effect HTTP API or RPC on the default runtime | aws-lambda-httpapi, aws-lambda-rpc, gcp-cloud-run-api |
| Queue | a producer and a consumer through an event source | gcp-pubsub-worker, gcp-pubsub-fanout |
| Scheduled job | a cron trigger | gcp-cron, gcp-scheduled-job |
| Storage events | a function triggered by an upload | gcp-storage-uploads, gcp-eventarc-firestore |
| Durable workflows | alchemy/Workflows on the provider's engine (Step 5): start, signal, read a run | no example yet; copy the conformance fixtures in packages/integration-tests/test/Cloudflare/Workers/fixtures/durable-workflow/ |
| Containers | a container service | aws-ecs, fly-service |
| Kubernetes | a Deployment on the cloud's managed cluster | aws-eks, gcp-gke |
| VMs | a VM running a program | aws-ec2, hetzner-server |
| SQL database | a database with migrations, from a runtime | aws-rds, gcp-cloud-sql-drizzle |
| Cache | Redis from a runtime | gcp-memorystore-redis, fly-redis |
| Secrets | a secret read by a runtime | gcp-secrets-kms |
| AI | the cloud's model service | aws-bedrock-ai, gcp-vertex-ai |
| Service to service | two runtimes calling each other | gcp-service-to-service |
| Local dev | the same app under alchemy dev | aws-dev, cloudflare-dev |
Name examples <provider>-<workload> in kebab case. Each one is a
workspace project with the layout of examples/gcp-pubsub-worker:
package.json(private,deploy/dev/destroy/testscripts,workspace:*dependencies),tsconfig.jsonreferencingpackages/alchemyand the provider package, and aREADME.md.alchemy.run.ts, with the runtime and resources insrc/.test/integ.test.tswith@alchemy.run/test/Bun: deploy inbeforeAll, call the deployed app over HTTP, verify cloud state through distilled, destroy inafterAll. Websites anddev-capable apps also gettest/dev.test.ts.
Register every example in the root tsconfig.json references and in
scripts/test-examples.ts, and run its test against the testing profile
before you call it done. Track examples as rows in INDEX.md like any
other deliverable.
A draft PR for an early wave is fine; its description states the index counts and that the provider is incomplete.
A provider is done when:
- every SDK service is cataloged and every operation is classified;
- every in-scope resource has its own passing live test (or is
blocked: cost/blocked: skippedand the user has the list); - every
bindingoperation is a least-privilege binding over distilled, goes through<Provider>Bindings.Bind, reaches foreign hosts, and is called from a deployed host in a test; - no alchemy code makes a hand-written API call (review.md's "Hand-written HTTP" check is clean);
- every runtime ships both forms with
InferEnv, its event sources, and tests for each form; - the cloud's durable engine has an Effect workflow engine that passes the conformance cases, or is out of scope with a reason;
- the framework websites (if the cloud hosts websites) deploy to its CDN and functions;
- the example set (one per framework, one per workload) deploys and passes;
- JSDoc meets the bar in docs.md, the reference docs are generated, and the hub matches docs.md;
- the PR description shows the runtimes and the fat head;
- the review gate is clean, the test account is clean after a full run, and every SDK mismatch found along the way is a merged distilled patch.
Trending
More talk lately.about the whole repository
Who's talking about alchemy-run/alchemy
- alchemy-run/floci“feat: 100% alchemy local AWS coverage”GitHub · Aug 25, 2026 · 3 reactions
- A Hacker News story“Infrastructure as TypeScript”Hacker News · Oct 7, 2025 · 2 points
- sinequix/enrailar“build(deps): bump alchemy from 2.0.0-beta.81 to 2.0.0-beta.84”GitHub · Oct 23, 2026 · 1 reactions
- alchemy-run/distilled“Default retry policy has no per-attempt timeout: 9 hanging 504s block a caller ~10 min”GitHub · Oct 20, 2026 · 1 reactions
- maelstrom-co/maelstrom“build(infra): upgrade alchemy to beta.83”GitHub · Oct 17, 2026 · 1 reactions
The biggest 5 of 45 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 45
Talked about on GitHub, Hacker News and Bluesky (45 times).
On GitHub · 43
- Aug 25, 2026feat: 100% alchemy local AWS coverage — alchemy-run/flocisays it tested it3 reactions · 1 comment
- Oct 23, 2026build(deps): bump alchemy from 2.0.0-beta.81 to 2.0.0-beta.84 — sinequix/enrailar1 reaction · 7 comments
- Oct 20, 2026Default retry policy has no per-attempt timeout: 9 hanging 504s block a caller ~10 min — alchemy-run/distilled1 reaction · 1 comment
- Oct 17, 2026build(infra): upgrade alchemy to beta.83 — maelstrom-co/maelstrom1 reaction · 6 comments
- Jun 5, 2026Bump alchemy from 0.87.0 to 0.93.11 — martinthommesen/snow-mcp1 reaction · 1 comment
38 more on GitHub
- Feb 5, 2026feat(cloudflare): send email binding — alchemy-run/alchemy-async1 reaction · 4 comments
- Dec 15, 2025[10021] Uncaught Error: internal error; — alchemy-run/alchemy-async1 reaction · 7 comments
- Nov 23, 2025chore(deps): bump the minor-and-patch group across 1 directory with 12 updates — codesbyayush/no-name-yet1 reaction · 2 comments
- Oct 28, 2025fix(aws): export all aws resources and add aws/ec2 — alchemy-run/alchemy-async1 reaction · 2 comments
- Oct 29, 2026chore(deps): bump undici, wrangler and alchemy — dashayushman/namaste-ji-boilerplate0 reactions
- Oct 29, 2026chore(deps): bump the dependencies group across 1 directory with 39 updates — yielded-dev/agent0 reactions
- Oct 29, 2026ci: bump the dependencies group across 1 directory with 23 updates — yielded-dev/auth0 reactions
- Oct 29, 2026[Hyperdrive] Document what happens to an open transaction when the client request is aborted — cloudflare/cloudflare-docs0 reactions
- Oct 28, 2026Bump alchemy from 2.0.0-beta.79 to 2.0.0-beta.84 — marcosvrs/bellwatch0 reactions · 1 comment
- Oct 28, 2026fix(docs): repair moved Bun executable and Alchemy provider links — prisma/web0 reactions · 3 comments
- Oct 26, 2026separate package for aws s3 standalone client — alchemy-run/distilled0 reactions
- Oct 26, 2026chore: bump alchemy from 2.0.0-beta.83 to 2.0.0-beta.84 in the alchemy group across 1 directory — thomasfosterau/effect-auth0 reactions
- Oct 20, 2026cloudflare/r2: expose Local Uploads enable/disable — alchemy-run/distilled0 reactions · 1 comment
- Oct 19, 2026chore(repo): vendor latest Effect and Alchemy subtrees — systemfsoftware/are-the-types-wrong-effect0 reactions · 1 comment
- Oct 13, 2026chore(deps): bump alchemy from 2.0.0-beta.79 to 2.0.0-beta.81 — plannotator/artifact-server0 reactions
- Oct 10, 2026chore(deps-dev): bump the all-deps group across 1 directory with 6 updates — empotts/safety-car0 reactions · 1 comment
- Oct 9, 2026chore(deps): bump alchemy from 2.0.0-beta.79 to 2.0.0-beta.81 in the alchemy group — levinbaenninger/echo0 reactions · 1 comment
- Oct 8, 2026chore(deps-dev): bump alchemy from 2.0.0-beta.80 to 2.0.0-beta.81 — ImArtisann/repo-int0 reactions
- Sep 21, 2026整理現有 Durable Object git 伺服器的做法:資料怎麼存、支援哪些協定、遇過哪些限制 — yurenju/fugitive0 reactions · 1 comment
- Sep 1, 2026[EPIC] Effect v4 package architecture, Alchemy deployments, and runtime ecosystem — In-Time-Tec/generalist0 reactions · 2 comments
- Sep 1, 2026[P1] alchemy: add an Effect-native Generalist deployment integration — In-Time-Tec/generalist0 reactions · 2 comments
- Sep 1, 2026[P1] compatibility: publish and enforce one tested Effect v4 package cohort — In-Time-Tec/generalistsays it tested it0 reactions · 2 comments
- Aug 21, 2026Add Alchemy v2 Cloudflare deployment support — lloydrichards/stack-effect0 reactions · 1 comment
- Aug 4, 2026Requested SDKs — alchemy-run/distilled0 reactions
- Jul 21, 2026use alchemy's new gh environment resource — crosshatch/crosshatch0 reactions
- Jul 18, 2026Panic — FullStack-Flow/calorie-ai0 reactions
- Jun 27, 2026Segmentation fault at address 0x1800000011 — alchemy-run/alchemy-async0 reactions · 1 comment
- May 11, 2026chore(deps-dev): bump alchemy from 0.84.0 to 0.93.7 — acoyfellow/unsurf0 reactions
- May 1, 2026chore(deps): bump alchemy from 0.90.1 to 0.93.6 — ahargunyllib/realm0 reactions
- Apr 29, 2026chore(deps): bump alchemy from 0.91.2 to 0.93.6 — acoyfellow/lab0 reactions
- Apr 10, 2026Slow HMR updates for Vite and Cloudflare — AmanVarshney01/create-better-t-stack0 reactions · 2 comments
- Mar 29, 2026Alchemy commands can not be used with Vite+ — alchemy-run/alchemy-async0 reactions · 4 comments
- Feb 28, 2026chore(deps): bump the cloudflare group across 1 directory with 2 updates — StephenODea54/from-juice0 reactions
- Feb 11, 2026Tunnel wildcard ingress does not auto-create wildcard DNS CNAME (request support) — alchemy-run/alchemy-async0 reactions · 1 comment
- Dec 6, 2025Alchemy R2Bucket types vs `@cloudflare/workers-types` — alchemy-run/alchemy-async0 reactions · 2 comments
- Dec 2, 2025build(deps): bump alchemy from 0.78.0 to 0.79.0 — wyze/dev0 reactions
- Oct 26, 2025fix(cloudflare): convert maxWaitTimeMs to seconds in miniflare — brendadeeznuts1111/alchmenyrun0 reactions · 5 comments
- Oct 26, 2025chore(deps): prepare for Alchemy 0.77.0 upgrade — brendadeeznuts1111/alchmenyrun0 reactions · 2 comments
On Hacker News · 1
- Oct 7, 2025Infrastructure as TypeScript2 points
On Bluesky · 1
- Dec 4, 2025https://github.com/alchemy-run/alchemy/ — @tom-doerr.bsky.social1 likes and repost
Other versions1 project uses a version of this.
Word for word · 1
- alchemy-run/alchemy-effect★ 1.8k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository6 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- auditSkill
- prepare-releaseSkill
- pr-queueSkill
- release-overnightSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

