Every deploy lesson yield*-ed a given resource — Runtime, Gateway, Memory — as if Alchemy had always shipped them. It doesn't; the course does. This appendix opens the hood: how an Alchemy resource is authored against the AWS control-plane SDK, the same way Alchemy authors its own.
Alchemy has no AgentCore resource yet, and no CloudFormation escape hatch to borrow one — its resources are hand-authored against the AWS API. So the course ships AgentCore.Runtime as library code you consume like any other resource. You never have to read this lesson to finish the course. But authoring a resource is the moment Alchemy stops being magic: a resource is just an Effect that reconciles desired state against what AWS actually has. Once you've seen it, the alpha frontier stops being a wall.
given/agentcore/Runtime.ts
Every Alchemy resource is two values. A Resource is a typed handle — a name, its props, its output attributes. A Provider is the lifecycle that backs it: how to read current state, decide what changed, converge it, and delete it. This is exactly how Alchemy authors its own resources; the canonical reference is its SQS queue:
// the framework's own pattern — alchemy/AWS/SQS/Queue.ts
export const Queue = Resource<Queue>("AWS.SQS.Queue");
export const QueueProvider = () =>
Provider.effect(Queue, Effect.gen(function* () {
const region = yield* Region;
return Queue.Provider.of({
stables: ["queueName", "queueUrl", "queueArn"],
read: Effect.fn(function* ({ output }) { /* GetQueueUrl */ }),
diff: Effect.fn(function* ({ news, olds }) { /* replace vs update */ }),
reconcile: Effect.fn(function* ({ news, output }) { /* observe → ensure → sync */ }),
delete: Effect.fn(function* ({ output }) { /* DeleteQueue */ }),
});
}));
Our AgentCore.Runtime is the same skeleton with a different SDK underneath. The RuntimeProps (container URI, role ARN, network) and RuntimeOutput (agentRuntimeArn, agentRuntimeId, status) are already defined in the given file — what remains is the provider.
alchemy/AWS/SQS/Queue.ts — the canonical Resource+Provider shape
The SDK we author against — @distilled.cloud/aws/bedrock-agentcore-control — returns Effects, not Promises. Each operation is an OperationMethod requiring Credentials | Region | HttpClient, which the provider already has in scope:
import * as control from "@distilled.cloud/aws/bedrock-agentcore-control";
control.createAgentRuntime(req) // Effect<CreateAgentRuntimeResponse, CreateAgentRuntimeError, Credentials | Region | HttpClient>
control.getAgentRuntime(req) // Effect<GetAgentRuntimeResponse, ...>
control.deleteAgentRuntime(req) // Effect<DeleteAgentRuntimeResponse, ...>
This course's use pattern exists to wrap Promise-based SDKs — Effect.tryPromise, a service tag, a layer. Here there is nothing to wrap: createAgentRuntime is already an Effect with typed errors and its dependencies in the requirement channel. So we call it directly. The pattern still applies the moment you reach a Promise SDK; this SDK simply spares you it. Worth saying out loud, because it's the rare case where "wrap your client" is the wrong advice.
The heart of a provider is reconcile — given the desired props and any prior output, make AWS match. The shape for the Runtime: look it up; if it doesn't exist, create it; if it does, it's already converged. createAgentRuntime takes the container artifact, the role, and a network configuration:
reconcile: Effect.fn(function* ({ id, news, output }) {
// observe: does it already exist?
if (output?.agentRuntimeId) {
const got = yield* control.getAgentRuntime({ agentRuntimeId: output.agentRuntimeId });
return { agentRuntimeArn: got.agentRuntimeArn, agentRuntimeId: got.agentRuntimeId, status: got.status };
}
// ensure: create it
const created = yield* control.createAgentRuntime({
agentRuntimeName: id,
agentRuntimeArtifact: { containerConfiguration: { containerUri: news.containerUri } },
roleArn: news.roleArn,
networkConfiguration: { networkMode: news.network ?? "PUBLIC" },
environmentVariables: news.environment,
});
return {
agentRuntimeArn: created.agentRuntimeArn,
agentRuntimeId: created.agentRuntimeId,
status: created.status, // CREATING → READY
};
}),
read (used to detect drift outside Alchemy) is just the getAgentRuntime branch; delete is deleteAgentRuntime({ agentRuntimeId }), catching "already gone" the way Queue catches QueueDoesNotExist.
agentRuntimeArtifact, networkConfiguration, and the exact create/get/delete field names are the young part of this API. The names above follow @distilled.cloud/aws's CreateAgentRuntimeRequest (agentRuntimeName, agentRuntimeArtifact, roleArn, networkConfiguration) — but pin the version and read the installed .d.ts before trusting the nested shapes. This is exactly why the resource is given and dated: you patch one file, not ten lessons.
Some changes can be applied in place; others require tearing the resource down and rebuilding it. stables lists the output attributes that identify the resource — change an input that would alter them, and Alchemy must replace rather than update. For the Runtime:
stables: ["agentRuntimeArn", "agentRuntimeId"],
diff: Effect.fn(function* ({ news, olds }) {
// a new container image is an in-place update; changing the role identity replaces
if (news.roleArn !== olds.roleArn) return { action: "replace" } as const;
return undefined; // otherwise: update in place (new image digest, env vars)
}),
This is what made "re-run alchemy deploy" behave sensibly back in lesson 05: an unchanged stack is a no-op because diff returns nothing; a new image updates in place; a change to an identity-bearing input forces a clean replace. The idempotency you relied on is this function.
control ops: @distilled.cloud/aws/bedrock-agentcore-control — createAgentRuntime, getAgentRuntime, deleteAgentRuntime (+ createGateway, createMemory for the sibling resources)
A provider is a Layer. To make yield* Runtime("Agent", {...}) resolve inside the stack, its provider joins the collection alongside the AWS providers:
// the stack from lessons 04–08
export default Alchemy.Stack("EffectAgentCore",
{
providers: Layer.mergeAll(AWS.providers(), RuntimeProvider(), GatewayProvider(), MemoryProvider()),
state: Alchemy.localState(),
},
Effect.gen(function* () {
const runtime = yield* Runtime("Agent", { containerUri, roleArn }); // ← now resolvable
// ...
}),
);
There was never a privileged layer. The AgentCore.Runtime you've been deploying since lesson 05 is the same kind of value as AWS.ECR.Repository — a Resource plus a Provider that reconciles desired state against the SDK. "Given" meant "pre-written," not "special." You could author the next AWS resource AWS hasn't gotten to yet, the same way.
That closes the course. You built an Effect-docs agent as an HttpApi, gave it a model, deployed it to AgentCore, grew it with a Gateway tool and Memory, made it observable, and shipped the whole thing — site included — from one Effect program. And now the one piece that looked like infrastructure-magic is just more of the same program.