Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3743d7d
perf: split the Daimon runtime image into cacheable layers
apresmoi Aug 30, 2026
d056cc9
test: guarantee the Daimon runtime image layer graph and run it in CI
apresmoi Aug 30, 2026
df0c4c7
fix: raise workspace bundle size cap
apresmoi Aug 30, 2026
f436fc7
fix: raise workspace bundle entry cap
apresmoi Aug 30, 2026
84ee1c7
fix: provision Daimon broker files before tightening directory
apresmoi Aug 30, 2026
2db5386
fix: make Daimon provisioning capability-safe
apresmoi Aug 30, 2026
c79c717
fix: complete Daimon capability-safe provisioning
apresmoi Aug 30, 2026
b582905
fix: add SETPCAP to Daimon containers
apresmoi Aug 30, 2026
f33a41e
fix: grant Daimon supervisors CAP_KILL
apresmoi Aug 30, 2026
6089a31
fix: provision Daimon wake fuse ledger
apresmoi Aug 30, 2026
6b35e52
fix: let the daimon host and broker reach the files the wake fuse and…
apresmoi Aug 31, 2026
2232709
fix: give the grok worker writable hook state without unlocking its s…
apresmoi Aug 31, 2026
201d028
feat: build and run the local daimon runtime natively on arm64
apresmoi Sep 1, 2026
8a73cdb
docs: move the usage accounting spec into specs and fix its cross-rep…
apresmoi Sep 1, 2026
56a0d2b
fix: raise daimon instruction codepoint limit to match the byte cap
apresmoi Sep 1, 2026
593de5c
fix: provision the usage ledger volume the way the wake fuse director…
apresmoi Sep 2, 2026
67a56ad
fix: skip Moltnet skill install for Daimon agents, who use native tools
apresmoi Sep 2, 2026
9857de5
feat: lower schedule.jitter_seconds into the Daimon organization runt…
apresmoi Sep 2, 2026
1160656
fix: let the grok worker read its own config directory for the sandbo…
apresmoi Sep 3, 2026
7e35c28
fix: stop denying Grok workers from paths unix permissions already deny
apresmoi Sep 3, 2026
8f6f7ad
fix(test): type the schedule-authority helper to the resolved schedul…
apresmoi Sep 4, 2026
9099660
test: assert the native-by-default local build architecture contract
apresmoi Sep 4, 2026
bc339a7
Merge remote-tracking branch 'origin/main' into fix/daimon-broker-reg…
apresmoi Sep 4, 2026
f610908
test: complete the durable-volume Daimon fixture into a valid organiz…
apresmoi Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
- name: Verify native helper syscalls
run: node --test scripts/native-helper-artifacts.test.mjs scripts/native-helper-integration.test.mjs

- name: Verify Daimon image layering
run: node --test scripts/build-local-daimon-runtime.test.mjs

# Preseed reads the candidate volume through its Docker Mountpoint and
# relies on host rename/fsync/hardlink semantics a container cannot give
# it. On a rootful Linux daemon /var/lib/docker/volumes/*/_data is
Expand Down
116 changes: 85 additions & 31 deletions runtime-images/daimon/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# syntax=docker/dockerfile:1

ARG DAIMON_DEPENDENCY_MODE=registry
ARG NODE_BASE_IMAGE=node:24-bookworm-slim@sha256:a9f5f7c91a432850b2a8a7797adf5eadb6c733ceed61167806cee7ea7fbc29df
FROM daimon_package AS daimon_package

Expand All @@ -18,9 +19,92 @@ RUN test "$(sha256sum /tmp/daimon.tgz | awk '{print "sha256:" $1}')" = "${DAIMON
&& test -f /probe/node_modules/@noopolis/daimon/dist/runtime/contract-manifest.json \
&& cp /tmp/source-inputs.json /probe/source-inputs.json

FROM ${NODE_BASE_IMAGE} AS build
FROM ${NODE_BASE_IMAGE} AS base_offline-bundle

FROM ${NODE_BASE_IMAGE} AS base_registry
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*

FROM base_${DAIMON_DEPENDENCY_MODE} AS base

FROM base AS grok_source_registry
ARG GROK_CLI_URL
RUN curl -fsSL "${GROK_CLI_URL}" -o /tmp/grok

FROM base AS grok_source_offline-bundle
COPY --from=daimon_package /grok /tmp/grok

FROM grok_source_${DAIMON_DEPENDENCY_MODE} AS grok_cli
ARG GROK_CLI_SHA256
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
RUN echo "${GROK_CLI_SHA256} /tmp/grok" | sha256sum -c - \
&& mkdir -p ${RUNTIME_ROOT}/bin \
&& install -m 0755 /tmp/grok ${RUNTIME_ROOT}/bin/grok \
&& test "$(sha256sum ${RUNTIME_ROOT}/bin/grok | awk '{print "sha256:" $1}')" = "sha256:${GROK_CLI_SHA256#sha256:}" \
&& rm -f /tmp/grok

FROM grok_cli AS agy_source_registry
ARG AGY_CLI_URL
RUN curl -fsSL "${AGY_CLI_URL}" -o /tmp/agy.tar.gz

FROM grok_cli AS agy_source_offline-bundle
COPY --from=daimon_package /agy.tar.gz /tmp/agy.tar.gz

FROM agy_source_${DAIMON_DEPENDENCY_MODE} AS agy_cli
ARG AGY_CLI_SHA512
ARG AGY_CLI_SHA256
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
RUN echo "${AGY_CLI_SHA512} /tmp/agy.tar.gz" | sha512sum -c - \
&& rm -rf /tmp/agy-extract \
&& mkdir -p /tmp/agy-extract \
&& tar -xzf /tmp/agy.tar.gz -C /tmp/agy-extract \
&& agy_path="$(find /tmp/agy-extract -type f -name antigravity -print -quit)" \
&& test -n "${agy_path}" \
&& install -m 0755 "${agy_path}" ${RUNTIME_ROOT}/bin/agy \
&& test "$(sha256sum ${RUNTIME_ROOT}/bin/agy | awk '{print "sha256:" $1}')" = "sha256:${AGY_CLI_SHA256#sha256:}" \
&& rm -rf /tmp/agy.tar.gz /tmp/agy-extract

FROM agy_cli AS codex_registry
ARG CODEX_CLI_VERSION=0.142.3
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
cd ${RUNTIME_ROOT} \
&& npm install --omit=dev --no-fund --no-audit @openai/codex@${CODEX_CLI_VERSION}

FROM agy_cli AS codex_offline-bundle
ARG DAIMON_DEPENDENCY_ARCHIVE_SHA256=none
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
COPY --from=daimon_package /dependencies.tar /tmp/dependencies.tar
RUN test "$(sha256sum /tmp/dependencies.tar | awk '{print "sha256:" $1}')" = "${DAIMON_DEPENDENCY_ARCHIVE_SHA256}" \
&& mkdir -p ${RUNTIME_ROOT}/node_modules \
&& tar -xf /tmp/dependencies.tar -C ${RUNTIME_ROOT}/node_modules \
&& test -x ${RUNTIME_ROOT}/node_modules/@openai/codex/bin/codex.js \
&& rm -f /tmp/dependencies.tar

FROM codex_registry AS daimon_registry
ARG DAIMON_PACKAGE_SHA256
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
COPY --from=daimon_package /daimon.tgz /tmp/daimon.tgz
COPY --from=daimon_package /source-inputs.json /tmp/source-inputs.json
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
test "$(sha256sum /tmp/daimon.tgz | awk '{print "sha256:" $1}')" = "${DAIMON_PACKAGE_SHA256}" \
&& cd ${RUNTIME_ROOT} \
&& npm install --omit=dev --no-fund --no-audit /tmp/daimon.tgz \
&& rm -f /tmp/daimon.tgz

FROM codex_offline-bundle AS daimon_offline-bundle
ARG DAIMON_PACKAGE_SHA256
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon
COPY --from=daimon_package /daimon.tgz /tmp/daimon.tgz
COPY --from=daimon_package /source-inputs.json /tmp/source-inputs.json
RUN test "$(sha256sum /tmp/daimon.tgz | awk '{print "sha256:" $1}')" = "${DAIMON_PACKAGE_SHA256}" \
&& mkdir -p ${RUNTIME_ROOT}/node_modules/@noopolis/daimon \
&& tar -xzf /tmp/daimon.tgz -C ${RUNTIME_ROOT}/node_modules/@noopolis/daimon --strip-components=1 \
&& rm -f /tmp/daimon.tgz

FROM daimon_${DAIMON_DEPENDENCY_MODE} AS build

ARG GROK_CLI_VERSION
ARG GROK_CLI_URL
ARG GROK_CLI_SHA256
Expand All @@ -38,12 +122,6 @@ ARG CODEX_CLI_SHA256
ARG TARGETARCH
ARG RUNTIME_ROOT=/opt/spawnfile/runtime-installs/daimon

COPY --from=daimon_package /daimon.tgz /tmp/daimon.tgz
COPY --from=daimon_package /dependencies.tar /tmp/dependencies.tar
COPY --from=daimon_package /source-inputs.json /tmp/source-inputs.json
COPY --from=daimon_package /grok /tmp/offline-grok
COPY --from=daimon_package /agy.tar.gz /tmp/offline-agy.tar.gz

RUN test -n "${GROK_CLI_VERSION}" \
&& test -n "${GROK_CLI_URL}" \
&& test -n "${GROK_CLI_SHA256}" \
Expand All @@ -58,28 +136,6 @@ RUN test -n "${GROK_CLI_VERSION}" \
&& { test "${DAIMON_DEPENDENCY_MODE}" = registry || test "${DAIMON_DEPENDENCY_MODE}" = offline-bundle; } \
&& test -n "${CODEX_CLI_SHA256}" \
&& test -n "${TARGETARCH}" \
&& test "$(sha256sum /tmp/daimon.tgz | awk '{print "sha256:" $1}')" = "${DAIMON_PACKAGE_SHA256}" \
&& if test "${DAIMON_DEPENDENCY_MODE}" = registry; then apt-get update && apt-get install --yes --no-install-recommends ca-certificates curl && rm -rf /var/lib/apt/lists/*; fi \
&& mkdir -p ${RUNTIME_ROOT}/bin \
&& cd ${RUNTIME_ROOT} \
&& if test "${DAIMON_DEPENDENCY_MODE}" = offline-bundle; then \
test "$(sha256sum /tmp/dependencies.tar | awk '{print "sha256:" $1}')" = "${DAIMON_DEPENDENCY_ARCHIVE_SHA256}" \
&& mkdir -p node_modules/@noopolis/daimon \
&& tar -xf /tmp/dependencies.tar -C node_modules \
&& tar -xzf /tmp/daimon.tgz -C node_modules/@noopolis/daimon --strip-components=1 \
&& test -x node_modules/@openai/codex/bin/codex.js; \
else npm install --omit=dev --no-fund --no-audit /tmp/daimon.tgz @openai/codex@${CODEX_CLI_VERSION}; fi \
&& if test "${DAIMON_DEPENDENCY_MODE}" = offline-bundle; then cp /tmp/offline-grok /tmp/grok; else curl -fsSL "${GROK_CLI_URL}" -o /tmp/grok; fi \
&& echo "${GROK_CLI_SHA256} /tmp/grok" | sha256sum -c - \
&& install -m 0755 /tmp/grok ${RUNTIME_ROOT}/bin/grok \
&& if test "${DAIMON_DEPENDENCY_MODE}" = offline-bundle; then cp /tmp/offline-agy.tar.gz /tmp/agy.tar.gz; else curl -fsSL "${AGY_CLI_URL}" -o /tmp/agy.tar.gz; fi \
&& echo "${AGY_CLI_SHA512} /tmp/agy.tar.gz" | sha512sum -c - \
&& rm -rf /tmp/agy-extract \
&& mkdir -p /tmp/agy-extract \
&& tar -xzf /tmp/agy.tar.gz -C /tmp/agy-extract \
&& agy_path="$(find /tmp/agy-extract -type f -name antigravity -print -quit)" \
&& test -n "${agy_path}" \
&& install -m 0755 "${agy_path}" ${RUNTIME_ROOT}/bin/agy \
&& test -x ${RUNTIME_ROOT}/node_modules/@openai/codex/bin/codex.js \
&& test -f ${RUNTIME_ROOT}/node_modules/@noopolis/daimon/dist/runtime/cli.js \
&& case "${TARGETARCH}" in \
Expand Down Expand Up @@ -108,8 +164,6 @@ RUN test -n "${GROK_CLI_VERSION}" \
&& test "$(sha256sum ${RUNTIME_ROOT}/bin/daimon-engine-broker | awk '{print $1}')" = "${broker_sha}" \
&& test "$(sha256sum ${RUNTIME_ROOT}/bin/agy | awk '{print "sha256:" $1}')" = "sha256:${AGY_CLI_SHA256#sha256:}" \
&& node -e 'const fs=require("fs"),path=require("path"),root=path.resolve(process.argv[1]);let count=0;const walk=d=>{for(const n of fs.readdirSync(d)){const p=path.join(d,n),s=fs.lstatSync(p);if(s.isDirectory())walk(p);else if(s.isSymbolicLink()){if(++count>4096)throw Error("too many runtime links");const l=fs.readlinkSync(p);if(path.isAbsolute(l))throw Error("absolute runtime link");const r=fs.realpathSync(p);if(!r.startsWith(root+path.sep))throw Error("runtime link escape");const t=fs.statSync(p);if(!t.isFile()||t.dev!==fs.statSync(root).dev)throw Error("unsafe runtime link target");}}};walk(root)' ${RUNTIME_ROOT} \
&& rm -rf /tmp/agy.tar.gz /tmp/agy-extract /tmp/grok \
&& npm cache clean --force \
&& test -f ${RUNTIME_ROOT}/node_modules/@noopolis/daimon/package.json

FROM scratch
Expand Down
19 changes: 12 additions & 7 deletions scripts/build-local-daimon-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,16 @@ export const resolveLocalImageTag = (value) => {
return tag;
};

export const resolveLocalBuildArchitecture = (hostArchitecture) => {
export const resolveLocalBuildArchitecture = (hostArchitecture, requested = process.env.SPAWNFILE_DAIMON_TARGET_ARCH) => {
if (hostArchitecture !== "x64" && hostArchitecture !== "arm64") {
throw new Error("Local Daimon builds require an x64 or arm64 Docker host for the linux/amd64 artifact");
throw new Error("Local Daimon builds require an x64 or arm64 Docker host");
}
return "amd64";
if (requested !== undefined && requested !== "amd64" && requested !== "arm64") {
throw new Error(`Unsupported SPAWNFILE_DAIMON_TARGET_ARCH: ${requested}`);
}
// Default to the host architecture. Emulating amd64 on an arm64 host cannot create the
// user namespaces the Grok sandbox requires, so a native build is the working default.
return requested ?? (hostArchitecture === "arm64" ? "arm64" : "amd64");
};

export const resolvePushedImageReference = (imageTag, repoDigests) => {
Expand Down Expand Up @@ -159,13 +164,13 @@ const stageOfflineCliAssets = (directory, artifacts) => {
}
};

const stageBundleBuiltDaimon = (directory, artifacts) => {
const stageBundleBuiltDaimon = (directory, artifacts, architecture) => {
const sourceDirectory = path.join(directory, "source_bundle"), dependencyDirectory = path.join(directory, "dependency_bundle");
mkdirSync(sourceDirectory, { recursive: true }); mkdirSync(dependencyDirectory, { recursive: true });
const source = requiredBundle("SPAWNFILE_DAIMON_SOURCE_BUNDLE", "source.tar", sourceDirectory, "source");
const dependencies = requiredBundle("SPAWNFILE_DAIMON_DEPENDENCY_BUNDLE", "dependencies.tar", dependencyDirectory, "dependencies");
const output = path.join(directory, "package"); mkdirSync(output, { recursive: true });
execFileSync("docker", ["build", "--network=none", "--platform", "linux/amd64", "--build-context", `source_bundle=${sourceDirectory}`,
execFileSync("docker", ["build", "--network=none", "--platform", `linux/${architecture}`, "--build-context", `source_bundle=${sourceDirectory}`,
"--build-context", `dependency_bundle=${dependencyDirectory}`, "--output", `type=local,dest=${output}`,
"--build-arg", `SOURCE_ARCHIVE_SHA256=${source.archive_sha256}`, "--build-arg", `DEPENDENCY_ARCHIVE_SHA256=${dependencies.archive_sha256}`,
"--build-arg", `SOURCE_MANIFEST_SHA256=${source.manifest_sha256}`, "--build-arg", `DEPENDENCY_MANIFEST_SHA256=${dependencies.manifest_sha256}`,
Expand All @@ -181,7 +186,7 @@ const stageBundleBuiltDaimon = (directory, artifacts) => {
mode: "source-bundle", source: { archive_sha256: source.archive_sha256, manifest_sha256: source.manifest_sha256 },
version: "spawnfile.daimon-source-inputs.v1" };
const buildIdentity = JSON.parse(readFileSync(path.join(output, "source-inputs.json"), "utf8"));
if (buildIdentity.target !== "linux/amd64" || buildIdentity.source.archive_sha256 !== source.archive_sha256 || buildIdentity.source.manifest_sha256 !== source.manifest_sha256 ||
if (buildIdentity.target !== `linux/${architecture}` || buildIdentity.source.archive_sha256 !== source.archive_sha256 || buildIdentity.source.manifest_sha256 !== source.manifest_sha256 ||
buildIdentity.dependencies.archive_sha256 !== dependencies.archive_sha256 || buildIdentity.dependencies.manifest_sha256 !== dependencies.manifest_sha256) {
throw new Error("Remote bundle build source identity does not match its attested inputs");
}
Expand Down Expand Up @@ -234,7 +239,7 @@ const main = () => {
const artifacts = readDaimonCliArtifactPins();
const packageDirectory = mkdtempSync(path.join(os.tmpdir(), "spawnfile-daimon-package-"));
try {
const bundled = sourceMode === "source-bundle" ? stageBundleBuiltDaimon(packageDirectory, artifacts) : null;
const bundled = sourceMode === "source-bundle" ? stageBundleBuiltDaimon(packageDirectory, artifacts, architecture) : null;
const packagePath = bundled?.packagePath ?? stagePackagedDaimon(packageDirectory);
const manifestBytes = execFileSync("tar", ["-xOf", packagePath, "package/dist/runtime/contract-manifest.json"]);
const receipt = createLocalDaimonCapabilityReceipt({
Expand Down
Loading
Loading