AWS runner

Deploy a runner into your own AWS account, wire it to Evident, and understand exactly what it holds and what it costs.

This runner runs in your own AWS account, not on your machine and not on Evident's infrastructure — Evident boots and suspends it on demand instead of you running evident run yourself (see the local runner for that path).

Two strategies, one package

Both AWS strategies ship from a single published package, @evident-ai/runner-cdk (npm install @evident-ai/runner-cdk, no checkout required to install it).

  • Fargate — EvidentScaleToZeroConstruct. A long-running container that scales itself to zero when idle. Your runner image is any ecs.ContainerImage — a registry reference, no checkout of this repo needed. After installing, the package's own README at node_modules/@evident-ai/runner-cdk/README.md has a short walkthrough and the full props table — this page doesn't duplicate it. The long-form Fargate walkthrough isn't published publicly yet.
  • MicroVM — EvidentMicrovmConstruct. A per-session Lambda MicroVM that boots on demand and suspends between messages — the rest of this page. Evident publishes a multi-architecture base image to GHCR, and you extend it with a Dockerfile you control. stageMicrovmBuildContext() combines that build context with your full repository checkout before CDK uploads it.
View diagram source
graph TB
  subgraph AWS["your AWS account"]
    CDK["@evident-ai/runner-cdk · private preview"]
    CDK -->|"Fargate strategy"| Fargate["EvidentScaleToZeroConstruct — long-running container, scales to zero"]
    CDK -->|"MicroVM strategy"| MicroVM["EvidentMicrovmConstruct — controller + per-session Lambda MicroVM"]
  end
  Evident -->|"wake request via Function URL, HMAC-verified"| MicroVM
  Fargate -->|"runner outbound: conversational traffic"| Evident["Evident"]
  MicroVM -->|"runner outbound: conversational traffic"| Evident
Both strategies deploy inside your own AWS account; the runner reaches Evident outbound, while Evident uses a signed wake request to start the MicroVM.

What you get, and what stays yours

Evident never holds a key to your AWS account, and the infrastructure you deploy holds no Evident credential at rest. The only Evident secret it stores is a shared HMAC key, and its only job is verification: your controller uses it to check that a request really came from Evident before it starts or suspends anything. It grants no access to Evident, and no access to AWS.

A running runner is the one exception — stated plainly rather than softened. While a runner is running, it does hold one Evident credential: a runner key that Evident issues for that boot so the runner can call Evident back (that is how it streams your conversation and how it reconnects after a suspend). It is not your Evident account credential. It is scoped to that single runner, so it cannot touch any other runner, and it cannot manage your team, your billing or your API keys. It expires on its own within nine hours — longer than the eight hours AWS allows a MicroVM to live, so every cold start gets a fresh one — but nothing revokes it early when a VM stops; expiry is what ends it. And it travels with the VM rather than with your infrastructure: it is written inside the VM at boot and is present in the VM's suspended snapshot.

The runner only ever dials out to Evident — there is nothing inbound to open on your side. Model provider credentials and the runner's durable state stay in your account, in the S3 bucket the stack creates.

One more honest detail: although the HMAC key never appears in the CloudFormation template or its outputs, anyone in your account with lambda:GetFunctionConfiguration can read its resolved value from the controller Lambda's environment. That's acceptable because it is a verification key — it grants access to nothing of Evident's.

What isn't configurable yet

  • The workspace repository is fixed at image build time — it ships the repository you pass to stageMicrovmBuildContext(), checked out at the commit the image was built from. Any repository works. Dependency installation and build work belong in your customer-owned Dockerfile.
  • There is no per-runner repository setting yet.
  • Changing the workspace repository or Dockerfile requires rebuilding the image — about five minutes, via one cdk deploy.

Prerequisites

  • An AWS account with permission to create Lambda MicroVM images, Lambda functions, IAM roles, and an S3 bucket.
  • The AWS CLI configured for that account.
  • cdk bootstrap already run for that account and the region you'll deploy into.
  • Node.js and pnpm.
  • npm install @evident-ai/runner-cdk — it provides the construct and staging helper. No checkout of the Evident repository is required.
  • A full (non-shallow) clone of the repository your agent will work on, which is baked into the image as its workspace.
  • An Evident team where you are an admin or owner — turning on the run-payload option below is admin/owner gated.

Choose a region

The default region is eu-west-1. These are the regions Evident expects MicroVMs to run in today:

  • eu-west-1
  • us-east-1
  • us-east-2
  • us-west-2
  • ap-northeast-1

The region is whichever one your own stack deploys into — set it on the stack's env, as you would for any CDK stack. The construct adds no region check of its own, so deploying into a region without Lambda MicroVM support fails at the AWS API rather than at synth; check the list above first, and check AWS's own documentation if it looks out of date.

Step 1 — Create the pool and connector in Evident

  1. Create a runner pool in the Evident web app.
  2. Turn on Auto-provisioning for the pool — this routes one runner per person and marks the pool as MicroVM-backed.
  3. In the pool's Connectors section, add a Webhook connector:
    • Give it a placeholder HTTPS URL — you'll replace this with the real one in step 3.
    • Turn on the run-payload option — this is the one setting the whole thing depends on, and it needs an admin or owner to enable.
    • Leave "Message queued (automatic wake)" ticked — it's on by default, and it's what boots a runner for a new message. Don't use "Wake requested (manual wake button)" instead; that's for the manual wake button, a different use case. The form requires at least one event to save.
  4. Copy the signing secret now — it is shown exactly once. You'll need it in the next step.

You don't need to separately subscribe this connector to a suspend event — turning on the run-payload option already does that for you.

Step 2 — The one cdk deploy

Look up the managed base image for your region:

aws lambda-microvms list-managed-microvm-images --region <region>

These are service-managed images, so there's nothing to hardcode — always read the ARN and version from that call.

Then deploy your own CDK app — the one where you instantiated EvidentMicrovmConstruct, passing that ARN and version, the secret you copied in step 1, and a build context staged with stageMicrovmBuildContext(). Your Dockerfile must start from a pinned base image such as ghcr.io/sroze/evident-microvm-base:sha-3f3949d and contains your own dependency installation and build steps:

import {
   EvidentMicrovmConstruct,
   HOOKS_PORT,
   HOOK_TIMEOUT_SECONDS,
   stageMicrovmBuildContext,
 } from '@evident-ai/runner-cdk';

const buildContextPath = stageMicrovmBuildContext({
  buildContextPath: './microvm',
  workspaceRepositoryPath: './repo',
  workspaceOriginUrl: 'https://github.com/acme/widgets.git',
});

 new EvidentMicrovmConstruct(this, 'Runner', {
   buildContextPath,
  // Must match the published image these were baked into, so export them
  // rather than restating the numbers.
  hooksPort: HOOKS_PORT,
  hookTimeoutSeconds: HOOK_TIMEOUT_SECONDS,
  baseImageArn,
  baseImageVersion,
  controllerSigningSecret,
});

The staged checkout must be full rather than shallow because the agent branches and commits. Keep its origin credential-free: session credentials arrive through the lifecycle hook. The Dockerfile copies the staged workspace directory to /workspace, switches back to USER runner, and runs git reset --quiet before any workspace build.

npx cdk deploy

How you feed in the ARN, version and secret is your stack's call — literals, CfnParameters, or an SSM lookup. The construct takes plain strings and a Secrets Manager ISecret, deliberately, so it mints nothing into your stack. The first deploy stages the context, uploads the image as an S3 asset, builds the MicroVM image, and wires up the controller; expect about five to six minutes.

A later, routine redeploy is just cdk deploy again. Rotating the controller signing secret means updating the secret your stack passes in and redeploying. (If you wired the ARN, version and secret as CfnParameters, CloudFormation reuses their previous values on an update that omits them.)

Customising the image

The image you deploy combines the published base with your Dockerfile. Put operating-system installs and project build work in that Dockerfile, and keep secrets out of both the Docker build context and image environment.

FROM ghcr.io/sroze/evident-microvm-base:sha-3f3949d

USER root
RUN apt-get update \
  && apt-get install -y --no-install-recommends tree \
  && rm -rf /var/lib/apt/lists/*

COPY --chown=10001:10001 workspace /workspace
USER runner
WORKDIR /workspace
RUN git reset --quiet
RUN if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile; fi

If an installed tool needs a value at runtime, pass it through the construct's extraImageEnvironment prop. Every VM launched from that image version can read those values, so never put a secret there.

Adding packages or build work makes the image larger and slows the AWS image build. The image rebuilds the next time you run cdk deploy.

Step 3 — Paste the Function URL back into Evident

Once the deploy finishes, read the ControllerFunctionUrl output — from the CloudFormation console, or:

aws cloudformation describe-stacks \
  --stack-name <your stack name> \
  --query "Stacks[0].Outputs"

Edit the webhook connector's Endpoint URL to that value and save. This is the last wiring step — you don't need to re-enter the secret.

What Evident sets for you

  • Evident allocates the durable-state location each runner writes to.
  • Each cold boot gets a fresh runner key — the same short-lived, single-runner key described above, not a second thing.
  • Machine size defaults to the stack's default shape, and is chosen in the pool's settings.

Verification

Open the pool's settings and confirm the machine-size picker lists the sizes your stack advertises. If it does, Evident reached your controller and the signing key matched on both sides — that's a genuine end-to-end check, and it needs no AWS console.

You can also confirm the stack itself deployed correctly by checking its ControllerFunctionUrl, MicrovmShapes, and DurableStateBucketName outputs exist.

For the real end-to-end test, send a message to the pool. Expect a first boot of about 27 seconds, after which the runner comes online. It will boot successfully even without a model provider connected — that's expected, not a failure — but it needs one to actually answer; see model providers.

Troubleshooting

If the machine-size picker can't load shapes, here's what each case usually means:

What you see Likely cause
No shapes, no error No webhook connector with the run-payload option on for this pool yet.
"Connector disabled" The webhook connector is toggled off.
"URL not allowed" The endpoint URL isn't a public HTTPS URL.
"Couldn't reach the controller" Wrong URL, or the Lambda Function URL was removed — check the ControllerFunctionUrl output.
Controller responded, but with an error Usually a mismatched controller signing secret — for a caller-owned stack, inspect the Secrets Manager secret referenced by your own controllerSigningSecret stack input. Its JSON field must be CONTROLLER_SIGNING_SECRET. DoorbellSecret is relevant only to Evident's own default stack, where it is the logical ID of the SOPS-backed signing-secret resource. If an older secret still uses DOORBELL_SECRET, rename that field or the controller reads no key and every doorbell returns 401 at runtime.
"Not a valid controller" The endpoint isn't answering as an Evident MicroVM controller.

Cost

A runner suspends itself automatically about two minutes after it goes idle — there's nothing for you to switch on for this to happen. AWS terminates a MicroVM after at most eight hours, but that lifetime limit is not a resource or cost ceiling.

You configure a MicroVM's baseline memory and vCPU with minimumMemoryInMiB; the default 4,096 MiB shape runs at roughly ~$0.30/hour at that baseline. AWS automatically bursts a running MicroVM vertically — up to 4× its baseline — under load, billed at the same per-second rates only for what's actually used above baseline. You don't configure or trigger this burst yourself. For current unit rates and to measure your own usage, see AWS's MicroVM sizing table and the AWS Lambda pricing page.

  • Suspended: ~$0.0024/day, assuming roughly 0.75 GB of snapshot state — effectively free either way.
  • ~$0.004 per suspend/resume cycle (writing and reading that state).

When a runner reaches the lifetime limit

In two cases, nothing asks the runner to suspend and it runs until the eight-hour lifetime limit:

  • The webhook connector is switched off while a runner is live.
  • A runner dies without a clean shutdown — a crash, or a hard network drop — rather than exiting normally.

Two gaps we haven't closed: whether standing storage for the MicroVM image itself is billed is not yet established, and the S3 buckets plus the controller Lambda carry a small, nonzero cost beyond what's shown above.

Teardown

Before tearing anything down, let any running or suspended runner stop on its own. Then destroy the stack from your own CDK app:

npx cdk destroy

The durable-state S3 bucket is retained on purpose and survives cdk destroy, so it keeps costing you money until you delete it yourself. It's retained because it holds each runner's credentials and state, which deleting a stack must not throw away. Because the bucket is versioned, emptying it means deleting object versions, not just current objects.

The MicroVM image itself goes with the stack. The CDK bootstrap/staging bucket is separate and shared with other CDK apps in the account — don't delete it blindly.

Next steps