@evident-ai/runner-cdk 0.1.1-dev.da70cd4 → 3.4.1-dev.0ef5061
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -20
- package/dist/controller-lambda/handler.js +50529 -0
- package/dist/evident-scale-to-zero-construct.d.ts +16 -4
- package/dist/evident-scale-to-zero-construct.js +19 -14
- package/dist/image-version-reporter-lambda/handler.js +129 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +14 -1
- package/dist/microvm/constants.d.ts +7 -0
- package/dist/microvm/constants.js +34 -0
- package/dist/microvm/construct.d.ts +105 -0
- package/dist/microvm/construct.js +283 -0
- package/dist/microvm/controller/doorbell.d.ts +73 -0
- package/dist/microvm/controller/doorbell.js +107 -0
- package/dist/microvm/controller/handle-doorbell.d.ts +27 -0
- package/dist/microvm/controller/handle-doorbell.js +483 -0
- package/dist/microvm/controller/microvm-client.d.ts +81 -0
- package/dist/microvm/controller/microvm-client.js +7 -0
- package/dist/microvm/controller/shape-catalogue.d.ts +64 -0
- package/dist/microvm/controller/shape-catalogue.js +108 -0
- package/dist/microvm/controller/throttle-retry.d.ts +11 -0
- package/dist/microvm/controller/throttle-retry.js +27 -0
- package/dist/microvm/image/stage-context.d.ts +33 -0
- package/dist/microvm/image/stage-context.js +148 -0
- package/dist/microvm/image-version-reporter/construct.d.ts +35 -0
- package/dist/microvm/image-version-reporter/construct.js +91 -0
- package/dist/microvm/image-version-reporter/handler.d.ts +26 -0
- package/dist/microvm/image-version-reporter/handler.js +104 -0
- package/dist/microvm/shapes.d.ts +72 -0
- package/dist/microvm/shapes.js +93 -0
- package/dist/microvm-image-context/Dockerfile +227 -0
- package/dist/microvm-image-context/hook-server.js +286 -0
- package/dist/microvm-image-context/hooks/common.sh +957 -0
- package/dist/microvm-image-context/hooks/resume +78 -0
- package/dist/microvm-image-context/hooks/run +94 -0
- package/dist/microvm-image-context/hooks/suspend +22 -0
- package/dist/microvm-image-context/hooks/terminate +37 -0
- package/dist/waker/construct.js +1 -1
- package/package.json +15 -7
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Re-dial with the identity /run left behind. There is no step that could fetch
|
|
4
|
+
# a fresh runner key, so the one from /run is what resumes. The CLI delegated by
|
|
5
|
+
# start_tunnel owns the session-DB replicator's post-resume start. The interval
|
|
6
|
+
# credential sync loop (#1868 WI-3) stays here because /suspend stops it too, so
|
|
7
|
+
# a resumed VM that never restarted it here would never sync credentials again
|
|
8
|
+
# for the rest of its life.
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
# shellcheck source=./common.sh
|
|
12
|
+
source "$(dirname "$0")/common.sh"
|
|
13
|
+
|
|
14
|
+
# Unlike /run's cleanup, this stops ONLY the tunnel: opencode is inside the
|
|
15
|
+
# snapshot and resumes with it (README.md), already serving from before this
|
|
16
|
+
# hook ever ran, and killing it here would take down a session /run started.
|
|
17
|
+
# The only failure path today is the missing-context-file check below, before
|
|
18
|
+
# start_tunnel ever runs, where stop_tunnel is a cheap no-op. Kept for the same
|
|
19
|
+
# reason /run's trap is: a future failure path introduced between start_tunnel
|
|
20
|
+
# and resume_succeeded=true must not orphan the tunnel it just spawned.
|
|
21
|
+
resume_succeeded=false
|
|
22
|
+
cleanup() {
|
|
23
|
+
if [ "${resume_succeeded}" = true ]; then
|
|
24
|
+
return 0
|
|
25
|
+
fi
|
|
26
|
+
warn "resume failed; stopping the tunnel it started"
|
|
27
|
+
stop_tunnel || warn "stop_tunnel failed while cleaning up"
|
|
28
|
+
}
|
|
29
|
+
trap cleanup EXIT
|
|
30
|
+
|
|
31
|
+
if [ ! -s "${CONTEXT_FILE}" ]; then
|
|
32
|
+
warn "resume before run: no context at ${CONTEXT_FILE}"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
read -r api_url
|
|
38
|
+
read -r tunnel_url
|
|
39
|
+
read -r runner_key
|
|
40
|
+
} <"${CONTEXT_FILE}"
|
|
41
|
+
|
|
42
|
+
# Tolerant, never fatal: a resume that fails costs the user their whole
|
|
43
|
+
# session, so a broken durable-state config degrades to "no session-DB
|
|
44
|
+
# replication" rather than a failed resume. The CLI's own guards handle the
|
|
45
|
+
# rest — this needs no logic of its own.
|
|
46
|
+
load_state_config || warn "could not resolve durable-state config; the session DB will not resume replicating"
|
|
47
|
+
start_credential_sync
|
|
48
|
+
|
|
49
|
+
# Diagnostic-only, unlike /run's gate: a failed resume costs the user their
|
|
50
|
+
# whole session, so this never exits — it only converts a silent "resumed but
|
|
51
|
+
# the key is dead" into a named RUNNER-KEY-* cause in the log, whatever
|
|
52
|
+
# check_runner_key returns.
|
|
53
|
+
#
|
|
54
|
+
# And BACKGROUNDED for exactly that reason, following prewarm_litestream's
|
|
55
|
+
# pattern in common.sh: this is a network round trip bounded by the CLI's
|
|
56
|
+
# STATUS_TIMEOUT_MS (10 s), and nothing here consumes its answer. In the
|
|
57
|
+
# foreground it would sit between reading the context and `start_tunnel`,
|
|
58
|
+
# delaying the dial that IS this hook's job — by the most on exactly the
|
|
59
|
+
# unreachable API where the answer is worthless — against a resume this image
|
|
60
|
+
# measures in ~2 s (see the waker note in common.sh).
|
|
61
|
+
#
|
|
62
|
+
# Safe to leave running past the hook's own exit: the runtime waits on the
|
|
63
|
+
# script's `'exit'`, not `'close'` (aws/lambda-microvm-runtime/src/
|
|
64
|
+
# runtime.ts), so a lingering child never delays the HTTP response. Its stdio
|
|
65
|
+
# is deliberately NOT redirected — that inherited fd is how the RUNNER-KEY-*
|
|
66
|
+
# line reaches CloudWatch, which is the whole point of running it at all.
|
|
67
|
+
#
|
|
68
|
+
# The subshell is explicit rather than relying on `&` binding the whole AND-OR
|
|
69
|
+
# list, so a later edit cannot accidentally foreground the check again.
|
|
70
|
+
( check_runner_key "${runner_key}" "${api_url}" || true ) &
|
|
71
|
+
|
|
72
|
+
# Not waited on: the CLI backs off and keeps retrying its own dial with no
|
|
73
|
+
# attempt cap (apps/cli/src/lib/tunnel/connection.ts), and a control-plane
|
|
74
|
+
# reclaim pass (the api-worker lifecycle cron) is what decides whether a
|
|
75
|
+
# runner that never reconnects gets suspended.
|
|
76
|
+
start_tunnel "${runner_key}" "${api_url}" "${tunnel_url}"
|
|
77
|
+
|
|
78
|
+
resume_succeeded=true
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# The per-VM phase. EVERY per-VM and secret-bearing action happens here and
|
|
4
|
+
# nowhere earlier: the image snapshot is shared by every VM started from this
|
|
5
|
+
# image version, so anything done before /run would be identical across all of
|
|
6
|
+
# them — including, fatally, a runner identity.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# shellcheck source=./common.sh
|
|
10
|
+
source "$(dirname "$0")/common.sh"
|
|
11
|
+
|
|
12
|
+
# Anything this hook started must not outlive a failure: the next /run would
|
|
13
|
+
# find a stale opencode holding the port. On EXIT rather than ERR because an
|
|
14
|
+
# explicit `exit 1` — which is how every check below rejects — does NOT fire an
|
|
15
|
+
# ERR trap, and those are exactly the paths that have opencode running already.
|
|
16
|
+
# This also runs when the runtime signals an overrun hook, which it does with
|
|
17
|
+
# SIGTERM for exactly that reason: opencode and the tunnel are in a session of
|
|
18
|
+
# their own, so this trap is the only thing that can still reach them.
|
|
19
|
+
run_succeeded=false
|
|
20
|
+
cleanup() {
|
|
21
|
+
if [ "${run_succeeded}" = true ]; then
|
|
22
|
+
return 0
|
|
23
|
+
fi
|
|
24
|
+
warn "run failed; stopping what it started"
|
|
25
|
+
# Each stop tolerates its own failure: `set -e` aborts a trap function at the
|
|
26
|
+
# first non-zero command, which would skip the stop below it — precisely the
|
|
27
|
+
# orphan this trap exists to prevent.
|
|
28
|
+
stop_tunnel || warn "stop_tunnel failed while cleaning up"
|
|
29
|
+
stop_opencode || warn "stop_opencode failed while cleaning up"
|
|
30
|
+
kill_litestream || warn "kill_litestream failed while cleaning up"
|
|
31
|
+
stop_credential_sync || warn "stop_credential_sync failed while cleaning up"
|
|
32
|
+
}
|
|
33
|
+
trap cleanup EXIT
|
|
34
|
+
|
|
35
|
+
# The payload arrives on stdin, never argv.
|
|
36
|
+
payload="$(cat)"
|
|
37
|
+
|
|
38
|
+
# Never interpolate the payload into the message — it carries the runner key.
|
|
39
|
+
if ! payload_is_complete <<<"${payload}"; then
|
|
40
|
+
warn "run payload is missing runner_key, endpoints or state_prefix; refusing to dial"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# `@sh` quotes each value for the shell, so a hostile payload cannot inject.
|
|
45
|
+
eval "$(jq -er '@sh "runner_key=\(.runner_key) api_url=\(.endpoints.api) tunnel_url=\(.endpoints.tunnel) state_prefix=\(.state_prefix)"' <<<"${payload}")"
|
|
46
|
+
unset payload
|
|
47
|
+
|
|
48
|
+
# 1 — replace the identity the shared snapshot baked in.
|
|
49
|
+
regenerate_machine_id
|
|
50
|
+
|
|
51
|
+
# 2 — where this runner's credentials live. Recorded before the restore that
|
|
52
|
+
# reads it, and left behind for the hooks that flush back to the same prefix.
|
|
53
|
+
printf '%s\n' "${state_prefix}" >"${STATE_PREFIX_FILE}"
|
|
54
|
+
|
|
55
|
+
# 3 — start reading the litestream and aws CLI binaries NOW. The reads run in
|
|
56
|
+
# the background and overlap the CLI's own node startup and auth round trip,
|
|
57
|
+
# keeping their cold first touches off the path to the first credential fetch.
|
|
58
|
+
prewarm_litestream
|
|
59
|
+
prewarm_aws_cli
|
|
60
|
+
|
|
61
|
+
# 4 — export the durable-state config before anything that reads it. Fatal,
|
|
62
|
+
# because the CLI and the teardown hooks must agree on this runner's state prefix.
|
|
63
|
+
load_state_config || exit 1
|
|
64
|
+
|
|
65
|
+
# 5 — does the runner key in this payload actually authenticate against
|
|
66
|
+
# Evident? Checked here, before the tunnel spends any of this boot's time on a key
|
|
67
|
+
# that cannot work, and before anything has started that cleanup would need to
|
|
68
|
+
# tear down. Fatal ONLY on contrary evidence
|
|
69
|
+
# (check_runner_key's own contract, common.sh): a key the API actively rejects
|
|
70
|
+
# cannot work regardless, and failing here costs ~5s against the ~10 minutes a
|
|
71
|
+
# runner that can never connect would otherwise burn before the lifecycle cron
|
|
72
|
+
# reclaims it. Absent evidence (unreachable, no parseable JSON) is not fatal —
|
|
73
|
+
# it warns and this VM still boots.
|
|
74
|
+
check_runner_key "${runner_key}" "${api_url}" || exit 1
|
|
75
|
+
|
|
76
|
+
# 6 — the interval credential sync (#1868 WI-3). It stays before the context file
|
|
77
|
+
# is written. Bare: every guard inside start_credential_sync is its own `return 0`.
|
|
78
|
+
start_credential_sync
|
|
79
|
+
|
|
80
|
+
# 7 — the first per-VM identity on the wire. The subshell's umask makes the file
|
|
81
|
+
# unreadable to anyone else from the moment it exists, before the key is in it.
|
|
82
|
+
(
|
|
83
|
+
umask 077
|
|
84
|
+
printf '%s\n%s\n%s\n' "${api_url}" "${tunnel_url}" "${runner_key}" \
|
|
85
|
+
>"${CONTEXT_FILE}"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Not waited on: the CLI backs off and keeps retrying its own dial with no
|
|
89
|
+
# attempt cap (apps/cli/src/lib/tunnel/connection.ts), and a control-plane
|
|
90
|
+
# reclaim pass (the api-worker lifecycle cron) is what decides whether a
|
|
91
|
+
# runner that never connects gets suspended.
|
|
92
|
+
start_tunnel "${runner_key}" "${api_url}" "${tunnel_url}" true true
|
|
93
|
+
|
|
94
|
+
run_succeeded=true
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Flush anything durable, then drop the tunnel so the relay stops routing to a
|
|
4
|
+
# VM that is about to freeze. opencode stays up: it is inside the snapshot and
|
|
5
|
+
# resumes with it.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# shellcheck source=./common.sh
|
|
9
|
+
source "$(dirname "$0")/common.sh"
|
|
10
|
+
|
|
11
|
+
# Stopped BEFORE the boundary flush (#1868 WI-3): the interval loop and this
|
|
12
|
+
# flush must not race each other on the same credential stores.
|
|
13
|
+
stop_credential_sync
|
|
14
|
+
sync_credentials
|
|
15
|
+
stop_tunnel
|
|
16
|
+
|
|
17
|
+
# opencode is deliberately NOT stopped here (#812 WI-4) — it is inside the
|
|
18
|
+
# snapshot and must resume with it — so a turn still in flight may not be
|
|
19
|
+
# fully flushed by this call. Acceptable: the VM resumes with the local DB
|
|
20
|
+
# intact, and this flush exists for the TERMINATED-while-suspended case,
|
|
21
|
+
# where a frozen VM would otherwise flush nothing at all.
|
|
22
|
+
flush_session_db
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Best-effort teardown. The runtime reports 200 whatever happens here — the VM
|
|
4
|
+
# is going away either way — so each step is allowed to fail on its own without
|
|
5
|
+
# skipping the ones after it.
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
# shellcheck source=./common.sh
|
|
9
|
+
source "$(dirname "$0")/common.sh"
|
|
10
|
+
|
|
11
|
+
# Stopped BEFORE the boundary flush (#1868 WI-3): the interval loop and this
|
|
12
|
+
# flush must not race each other on the same credential stores.
|
|
13
|
+
stop_credential_sync
|
|
14
|
+
sync_credentials
|
|
15
|
+
stop_tunnel
|
|
16
|
+
|
|
17
|
+
# Writers stopped BEFORE litestream's final sync (#812 WI-4, the
|
|
18
|
+
# ordered-shutdown invariant, plan §6): otherwise litestream could snapshot
|
|
19
|
+
# while opencode is still writing its WAL and the last session writes would
|
|
20
|
+
# be missing from S3. stop_opencode_and_wait, not /run cleanup's cheap
|
|
21
|
+
# stop_opencode — /terminate can afford the real drain, the VM is being torn
|
|
22
|
+
# down either way.
|
|
23
|
+
stop_opencode_and_wait
|
|
24
|
+
flush_session_db
|
|
25
|
+
|
|
26
|
+
# The key must not outlive the VM's last useful moment. The no-replicate
|
|
27
|
+
# marker goes alongside it: no state that a later /resume could trust should
|
|
28
|
+
# outlive the VM's last useful moment either.
|
|
29
|
+
#
|
|
30
|
+
# The tunnel-ready marker used to be removed here too. It is gone entirely
|
|
31
|
+
# since #1172 — and naming an unset variable here is not a harmless leftover:
|
|
32
|
+
# this hook runs under `set -u` (above), so an unbound one aborts the script
|
|
33
|
+
# AT THIS LINE, leaving the runner key sitting in CONTEXT_FILE on a VM that is
|
|
34
|
+
# being torn down. That is the one thing this line exists to prevent.
|
|
35
|
+
rm -f "${CONTEXT_FILE}" "${SESSION_DB_NO_REPLICATE_MARKER}"
|
|
36
|
+
|
|
37
|
+
exit 0
|
package/dist/waker/construct.js
CHANGED
|
@@ -56,7 +56,7 @@ class EvidentWaker extends constructs_1.Construct {
|
|
|
56
56
|
runtime: lambda.Runtime.NODEJS_22_X,
|
|
57
57
|
// Pre-bundled at PACKAGE build time (`pnpm --filter @evident-ai/runner-cdk
|
|
58
58
|
// build`, see scripts/build.ts), not at synth time: esbuild inlines
|
|
59
|
-
// @evident/
|
|
59
|
+
// @evident/sdk's HMAC verifier into
|
|
60
60
|
// dist/waker-lambda/handler.js, so neither this package's published npm
|
|
61
61
|
// artifact (which ships dist/ already built) nor a consuming app needs
|
|
62
62
|
// that private workspace package, or esbuild, on synth's PATH. Requires
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evident-ai/runner-cdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Reusable CDK
|
|
3
|
+
"version": "3.4.1-dev.0ef5061",
|
|
4
|
+
"description": "Reusable CDK constructs for an Evident agent runner: a single scale-to-zero Fargate runner (task + service + per-agent self-stop role + waker Lambda), or a per-session AWS Lambda MicroVM that boots on demand and suspends between messages. Instantiate once per agent from your own stack.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -14,27 +14,35 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "ts-node scripts/build.ts",
|
|
17
|
+
"build": "pnpm run build-bundled-deps && ts-node scripts/build.ts",
|
|
18
|
+
"//build-bundled-deps": "scripts/build.ts esbuild-bundles runner/docker-images/microvm/hook-server.ts, which imports @evident-ai/lambda-microvm-runtime by its built `main`. @evident-ai/lambda-microvm-cdk is a workspace:* devDependency whose types resolve through its own dist/, so tsc --project tsconfig.build.json cannot compile src/microvm/{shapes,construct}.ts without it. Chained into `build` rather than left to turbo's `^build` because publish-runner-cdk.yaml and infrastructure/evident-runner's build-runner-cdk-dep invoke `pnpm --filter @evident-ai/runner-cdk build` directly, which bypasses turbo entirely.",
|
|
19
|
+
"build-bundled-deps": "pnpm --filter @evident-ai/lambda-microvm-cdk build && pnpm --filter @evident-ai/lambda-microvm-runtime build",
|
|
18
20
|
"typecheck": "tsc --noEmit",
|
|
19
21
|
"test": "node --test --require ts-node/register 'src/**/*.test.ts'",
|
|
20
22
|
"format": "prettier --write 'src/**/*.ts'",
|
|
21
23
|
"lint": "eslint 'src/**/*.ts' --max-warnings=0"
|
|
22
24
|
},
|
|
25
|
+
"//peerDependencies": "@evident-ai/lambda-microvm-cdk is a registry range here, never workspace:*: publish-runner-cdk.yaml ships this package with `npm publish`, not `pnpm publish`, so pnpm's workspace-protocol substitution never runs and a workspace: range would reach npm literally, breaking every external install. Enforced by src/published-surface.test.ts. The matching devDependency is workspace:* so this monorepo's own build compiles against aws/lambda-microvm-cdk's source.",
|
|
23
26
|
"peerDependencies": {
|
|
27
|
+
"@evident-ai/lambda-microvm-cdk": "^0.1.1",
|
|
24
28
|
"aws-cdk-lib": "^2.240.0",
|
|
25
29
|
"constructs": "^10.5.0"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@aws-sdk/client-ecs": "^3.682.0",
|
|
33
|
+
"@aws-sdk/client-lambda-microvms": "^3.1095.0",
|
|
29
34
|
"@aws-sdk/client-secrets-manager": "^3.682.0",
|
|
30
|
-
"@evident/
|
|
35
|
+
"@evident-ai/lambda-microvm-cdk": "workspace:*",
|
|
36
|
+
"@evident-ai/lambda-microvm-runtime": "workspace:*",
|
|
37
|
+
"@evident/sdk": "workspace:*",
|
|
31
38
|
"@types/node": "^22",
|
|
32
39
|
"aws-cdk-lib": "^2.240.0",
|
|
33
40
|
"constructs": "^10.5.0",
|
|
34
41
|
"esbuild": "^0.25.2",
|
|
35
42
|
"prettier": "^3.3.3",
|
|
36
43
|
"ts-node": "^10.9.2",
|
|
37
|
-
"typescript": "^5.6.3"
|
|
44
|
+
"typescript": "^5.6.3",
|
|
45
|
+
"runner-microvm-image": "workspace:*"
|
|
38
46
|
},
|
|
39
47
|
"publishConfig": {
|
|
40
48
|
"access": "public"
|
|
@@ -42,9 +50,9 @@
|
|
|
42
50
|
"repository": {
|
|
43
51
|
"type": "git",
|
|
44
52
|
"url": "https://github.com/sroze/evident.git",
|
|
45
|
-
"directory": "
|
|
53
|
+
"directory": "aws/runner-cdk"
|
|
46
54
|
},
|
|
47
55
|
"homepage": "https://evident.run",
|
|
48
|
-
"author": "Evident <
|
|
56
|
+
"author": "Evident <hello@evident.run>",
|
|
49
57
|
"license": "MIT"
|
|
50
58
|
}
|