Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
7e01937
feat: delegate canonical agent training to paideia
apresmoi Sep 13, 2026
275f20a
feat: forward bounded judge citation repairs
apresmoi Sep 13, 2026
5d72955
feat: delegate YAML training controls to Paideia
apresmoi Sep 14, 2026
938a771
feat: forward explicit training resume to Paideia
apresmoi Sep 14, 2026
a204615
feat: isolate training in one managed container
apresmoi Sep 14, 2026
c8c26df
fix: register training Python libraries for clean environments
apresmoi Sep 14, 2026
10afc12
feat: checkpoint declarative training preparation
apresmoi Sep 15, 2026
9ee5508
feat: complete declarative training image and input preparation
apresmoi Sep 15, 2026
e3244e3
fix: reject training output mismatch before preparation
apresmoi Sep 15, 2026
bbc5ec7
feat: authorize captured measurement repair forks
apresmoi Sep 15, 2026
afddd27
fix: forward paired measurement repair arguments
apresmoi Sep 15, 2026
47050be
fix: accept verified lineage in chained training repairs
apresmoi Sep 15, 2026
6c2bd45
perf: memoize training image seal digests by file identity
apresmoi Sep 17, 2026
be3dc7f
perf: replace recursive training image chmod with staged modes and or…
apresmoi Sep 17, 2026
3c399a5
test: isolate training dry-run seal memo assertion
apresmoi Sep 17, 2026
dcd874d
revert: remove training seal digest memo
apresmoi Sep 17, 2026
566308f
docs: state that a changed training distribution rebuilds later COPY …
apresmoi Sep 17, 2026
353b4f7
test: add docker-deferred training image mode equivalence check
apresmoi Sep 17, 2026
ba4c77c
feat: vendor the Daimon Grok 1.0.34 broker contract manifest and pin …
apresmoi Sep 17, 2026
1f3e3b7
feat: require a declared xAI model and reasoning effort for brokered …
apresmoi Sep 17, 2026
a960505
feat: emit no workspace skill roots for brokered Daimon Grok agents a…
apresmoi Sep 17, 2026
efac063
feat: vendor Daimon's Grok worker config renderer bytes and mirror it…
apresmoi Sep 17, 2026
bfaec6e
feat: provision brokered Grok workers from Daimon's pinned config byt…
apresmoi Sep 17, 2026
038bef0
feat: run Grok organizations under the pinned bubblewrap seccomp prof…
apresmoi Sep 17, 2026
3d1d3cd
feat: dedupe Daimon usage rows by turn and surface broker limit, mode…
apresmoi Sep 17, 2026
211b754
docs: describe Grok 1.0.34 brokered workers, container security, and …
apresmoi Sep 17, 2026
4bbd016
test: add the opt-in live check for a one-agent brokered Grok 1.0.34 …
apresmoi Sep 17, 2026
c86c5f7
docs: record the vendored Daimon Grok contract files in the Daimon ad…
apresmoi Sep 17, 2026
4ee37c9
fix: deny Grok workers every Moltnet, memory, mount, instance, and pe…
apresmoi Sep 17, 2026
fc3cc4d
fix: require the declared model and effort pin for provisioned Grok w…
apresmoi Sep 17, 2026
151e416
fix: mark estimated tokens in the usage engine rollup
apresmoi Sep 17, 2026
cd6e88d
fix: report differing usage rows under one turn key as a partial-cove…
apresmoi Sep 17, 2026
8ef468a
test: probe Moltnet and memory denies and the /var/run alias in the G…
apresmoi Sep 17, 2026
aa1e9a6
feat: re-vendor the Daimon Grok contract with worker temp, spill, inf…
apresmoi Sep 17, 2026
e6af844
feat: provision Grok worker private temp, closed shared temp, setgid …
apresmoi Sep 17, 2026
9c9e026
fix: refuse Grok worker deny entries equal to or above a base sandbox…
apresmoi Sep 17, 2026
3888748
test: prove an added ancestor covering a Daimon deny entry is refused
apresmoi Sep 17, 2026
eefa9fb
fix: write and validate canonical Grok registration paths within the …
apresmoi Sep 17, 2026
408848b
test: assert Grok temp, spill, and broker TMPDIR provisioning in the …
apresmoi Sep 17, 2026
051a5ce
docs: describe Grok worker temp, spill, and deny-grant rules
apresmoi Sep 17, 2026
521378b
Merge remote-tracking branch 'origin/feat/grok-lean-worker' into feat…
apresmoi Sep 17, 2026
a5bf084
refactor: share the Daimon broker root provisioning program and servi…
apresmoi Sep 17, 2026
e2a0c32
feat: provision, supervise and certify a broker-capable training Grok…
apresmoi Sep 17, 2026
46ad2b5
feat: launch spawnfile.training-container.v3 as root with a brokered …
apresmoi Sep 17, 2026
846794d
feat: import a dedicated training Grok login and refuse the desktop home
apresmoi Sep 17, 2026
a3177cd
test: pin the training slot privilege model, recycle order, canaries …
apresmoi Sep 17, 2026
3aee56f
fix: keep the training slot state root out of the deny list so masks …
apresmoi Sep 17, 2026
6f6a314
fix: order training slot provisioning so root without CAP_DAC_OVERRID…
apresmoi Sep 17, 2026
3822135
docs: specify the broker-capable training container, slot lifecycle a…
apresmoi Sep 17, 2026
907eb73
docs: drop the staged Grok credential and copied binary from the trai…
apresmoi Sep 17, 2026
7c0b10c
test: give the cold, warm and resume training preparation path an exp…
apresmoi Sep 17, 2026
430bf0a
docs: record the bubblewrap deny-target and root-capability constrain…
apresmoi Sep 17, 2026
40e8111
fix: order Grok worker temp and spill provisioning for a root without…
apresmoi Sep 17, 2026
21982cb
fix: place Grok worker deny entries where bubblewrap can materialize …
apresmoi Sep 17, 2026
d940b3e
fix: mask the training wake-acceptance store through its slot state r…
apresmoi Sep 17, 2026
2adc92c
docs: record the Grok deny-placement rule in the runtimes spec
apresmoi Sep 17, 2026
ccbe537
fix: pass the slot state root as the projection's acceptance-store mask
apresmoi Sep 17, 2026
dc9607d
fix: let the training image mode check run on a symlinked tmpdir and …
apresmoi Sep 17, 2026
d58ca42
Merge branch 'perf/training-warm-start' into feat/paideia-training-grok
apresmoi Sep 17, 2026
b521b2d
fix: install the native parent's pinned Grok and engine broker at the…
apresmoi Sep 17, 2026
028612f
test: derive the training image mode control from the recipe under test
apresmoi Sep 17, 2026
54d2095
test: pin the broker contract paths and the Daimon runtime link in th…
apresmoi Sep 17, 2026
5a3486d
fix: move the training broker temp out of the control root and clear …
apresmoi Sep 17, 2026
2f74b8b
test: cover training preparation scratch reuse and refusal through th…
apresmoi Sep 17, 2026
2789ea7
test: refuse a launch mount inside any training wipe or clear target
apresmoi Sep 17, 2026
2739714
docs: record the mount-inside-a-wipe-target rule and the preparation …
apresmoi Sep 17, 2026
b92faeb
feat: re-vendor the Daimon contract with the traverse-only organizati…
apresmoi Sep 17, 2026
b8bb63f
fix: keep every persistent mount inside a traversable Grok runtime ho…
apresmoi Sep 17, 2026
ac90cc6
test: assert private runtime-home mounts in the Grok live check
apresmoi Sep 17, 2026
abb29ce
Merge branch 'feat/grok-lean-worker' into feat/paideia-training-grok
apresmoi Sep 17, 2026
1822568
test: verify the traverse-only slot runtime home exposes only its set…
apresmoi Sep 17, 2026
3638201
chore: re-vendor Daimon's contract manifest after the launcher diagno…
apresmoi Sep 17, 2026
182231b
fix: carry declared broker limits and one evaluator root per role int…
apresmoi Sep 17, 2026
e9c2b4d
chore: re-vendor the Daimon Grok contract for the launcher's head-and…
apresmoi Sep 17, 2026
28dd710
chore: re-vendor the Daimon Grok contract for the launcher's blocking…
apresmoi Sep 18, 2026
691a333
chore: re-vendor the Daimon Grok contract for the worker retry ceilin…
apresmoi Sep 18, 2026
599287d
feat: bind a training run's declared native parent to its attested Da…
apresmoi Sep 18, 2026
30facc7
docs: specify the training native-parent and Daimon runtime identity …
apresmoi Sep 18, 2026
67e3f73
chore: re-vendor the Daimon contract after the launcher's output boun…
apresmoi Sep 18, 2026
1aa84be
fix: seal the training datasets with DAC on their ancestor and refuse…
apresmoi Sep 18, 2026
bbcc4f1
fix: canary the training slot and publish its preflight receipt befor…
apresmoi Sep 18, 2026
4cadee3
docs: specify the sealed-inputs DAC boundary and the start-up slot re…
apresmoi Sep 18, 2026
5debf84
docs: state the reachability limits of the refuse policy and the unse…
apresmoi Sep 18, 2026
f81ea52
fix: probe the sealed inputs from the worker's private tmp and record…
apresmoi Sep 18, 2026
f959cc8
docs: specify the seal probe verdicts, their mechanisms and the seale…
apresmoi Sep 18, 2026
4d4c31a
chore: re-vendor Daimon's runtime contract manifest at 36ce9f0
apresmoi Sep 18, 2026
98de641
chore: re-vendor Daimon's runtime contract manifest with the widened …
apresmoi Sep 18, 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Portability is capability-specific: the compiler reports each feature as **suppo
| Explore complete projects | [Examples](examples/) |
| Add an adapter or contribute | [Contributing](CONTRIBUTING.md) |
| Integrate with deployment tooling | [Target and lifecycle contracts](specs/TARGETS.md) |
| Plan isolated agent training | [Paideia training handoff](specs/TRAINING.md) |
| Find a detailed contract | [Specification index](specs/INDEX.md) |

Spawnfile is part of [Noopolis](https://github.com/noopolis). [Moltnet](https://moltnet.dev) supplies messaging; [Daimon](https://github.com/noopolis/daimon) runs individual agents; [Simfile](https://simfile.org) builds simulation worlds around organizations. You can use Spawnfile on its own.
Expand Down
3 changes: 3 additions & 0 deletions fixtures/grok-lean-worker/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Grok lean worker live check

You are a test agent. When woken, answer with the single word OK and call no tools.
22 changes: 22 additions & 0 deletions fixtures/grok-lean-worker/Spawnfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
spawnfile_version: "0.1"
kind: agent
name: grok-lean-worker
description: "One brokered Daimon Grok agent for the Grok 1.0.34 lean-worker live check"

runtime:
name: daimon
options:
engine: grok

execution:
model:
primary:
provider: xai
name: grok-4.6
auth:
method: grok
reasoning_effort: low

workspace:
docs:
system: AGENTS.md
20 changes: 17 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
},
"scripts": {
"compile:explicit-test-mcp": "node --experimental-strip-types scripts/compile-explicit-test-mcp.ts",
"build": "rm -rf dist && tsc --project tsconfig.build.json && chmod +x dist/cli/index.js && node --experimental-strip-types ./src/evidenceExportHelper/copyAssets.ts && node --experimental-strip-types ./src/runtime/copyScaffoldAssets.ts && node --experimental-strip-types ./src/deployment/native/copyArtifacts.ts",
"build": "rm -rf dist && tsc --project tsconfig.build.json && chmod +x dist/cli/index.js && node --experimental-strip-types ./src/evidenceExportHelper/copyAssets.ts && node --experimental-strip-types ./src/runtime/copyScaffoldAssets.ts && node --experimental-strip-types ./src/deployment/native/copyArtifacts.ts && node --experimental-strip-types ./src/compiler/training/preparation/copyAssets.ts",
"build:native": "node --experimental-strip-types ./src/deployment/native/build.ts",
"clean": "rm -rf coverage dist",
"coverage": "vitest run --coverage",
Expand All @@ -35,6 +35,7 @@
"audit:generate": "tsx src/audit/auditCli.ts",
"build:local-moltnet": "node --experimental-strip-types ./scripts/build-local-moltnet.ts",
"build:local-daimon": "node --experimental-strip-types ./scripts/build-local-daimon-runtime.ts",
"vendor:daimon-contract": "node --import tsx ./scripts/vendor-daimon-grok-contract.ts",
"bundle:source-provenance": "node --experimental-strip-types ./scripts/create-source-provenance-bundle.ts",
"prepare:linux-amd64-closure": "node --experimental-strip-types ./scripts/create-linux-amd64-dependency-closure.ts",
"prepare:linux-amd64-go-closure": "node --experimental-strip-types ./scripts/create-linux-amd64-go-closure.ts",
Expand All @@ -56,14 +57,16 @@
"test:coverage-verdict": "node --import tsx -e \"import fs from 'node:fs'; const file='coverage/coverage-summary.json'; const fail=(message)=>{ console.error('COVERAGE VERDICT: '+message); process.exit(1); }; let summary; try { summary=JSON.parse(fs.readFileSync(file, 'utf8')); } catch (error) { fail(error && error.code === 'ENOENT' ? 'summary file missing' : 'summary file unparseable'); } import('./vitest.config.ts').then(({default:config})=>{ const thresholds=config.test && config.test.coverage && config.test.coverage.thresholds; if (!thresholds || typeof thresholds !== 'object' || !summary || typeof summary !== 'object' || !summary.total || typeof summary.total !== 'object') fail('summary or threshold schema unrecognised'); const metricNames=['branches','functions','lines','statements']; const metrics=metricNames.filter((metric)=>Object.prototype.hasOwnProperty.call(thresholds, metric)); if (metrics.length === 0) fail('no threshold metrics configured'); let shortfall=false; for (const metric of metrics) { const threshold=thresholds[metric]; const pct=summary.total[metric] && summary.total[metric].pct; if (typeof threshold !== 'number' || !Number.isFinite(threshold) || typeof pct !== 'number' || !Number.isFinite(pct)) fail('missing or non-numeric value for '+metric); console.log('COVERAGE: '+metric+' '+pct+'% (threshold '+threshold+'%)'); if (pct < threshold) shortfall=true; } if (shortfall) { console.error('COVERAGE VERDICT: thresholds not met'); process.exit(2); } console.log('COVERAGE VERDICT: thresholds met'); }).catch((error)=>fail('could not load vitest config: '+error.message));\"",
"test:node": "mkdir -p coverage || exit 1; rm -f coverage/node-test.tap || exit 1; node --import tsx --test --test-reporter=tap src/runtime/pi/appControlDeliveryMetadata.test.ts > coverage/node-test.tap 2>&1; node_status=$?; cat coverage/node-test.tap; cat_status=$?; npm run test:node-verdict; verdict_status=$?; if [ \"$verdict_status\" -eq 2 ]; then exit 2; fi; if [ \"$verdict_status\" -ne 0 ]; then exit 1; fi; if [ \"$cat_status\" -ne 0 ]; then echo 'NODE VERDICT: could not read TAP output' >&2; exit 1; fi; if [ \"$node_status\" -ne 0 ]; then echo \"NODE VERDICT: node:test runner exited $node_status despite a passing TAP summary\" >&2; exit 1; fi; exit 0",
"test:node-verdict": "node -e \"const fs=require('fs'); const text=fs.readFileSync('coverage/node-test.tap', 'utf8'); const tests=text.match(/^# tests ([0-9]+)\\r?$/m); const suites=text.match(/^# suites ([0-9]+)\\r?$/m); const passed=text.match(/^# pass ([0-9]+)\\r?$/m); const failed=text.match(/^# fail ([0-9]+)\\r?$/m); const skipped=text.match(/^# skipped ([0-9]+)\\r?$/m); const todo=text.match(/^# todo ([0-9]+)\\r?$/m); const cancelled=text.match(/^# cancelled ([0-9]+)\\r?$/m); if (!tests || !suites || !passed || !failed || !skipped || !todo || !cancelled) { console.error('NODE VERDICT: TAP summary unrecognised'); process.exit(1); } const testCount=Number(tests[1]); const suiteCount=Number(suites[1]); const passCount=Number(passed[1]); const failCount=Number(failed[1]); const skippedCount=Number(skipped[1]); const todoCount=Number(todo[1]); const cancelledCount=Number(cancelled[1]); if (failCount > 0) { console.error('NODE VERDICT: test failures found (tests='+testCount+', failed='+failCount+')'); process.exit(2); } if (cancelledCount > 0) { console.error('NODE VERDICT: incomplete run (cancelled='+cancelledCount+')'); process.exit(1); } if (passCount + skippedCount + todoCount + cancelledCount !== testCount) { console.error('NODE VERDICT: TAP counts do not reconcile (tests='+testCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', cancelled='+cancelledCount+')'); process.exit(1); } if (testCount < 10) { console.error('NODE VERDICT: expected at least 10 node:test cases, found '+testCount+' — cases were removed or the file was gutted'); process.exit(1); } if (passCount < 10) { console.error('NODE VERDICT: expected at least 10 passing node:test cases, found '+passCount+' (skipped='+skippedCount+', todo='+todoCount+')'); process.exit(1); } if (suiteCount < 1) { console.error('NODE VERDICT: expected at least 1 node:test suite, found '+suiteCount); process.exit(1); } console.log('NODE VERDICT: tests passed (tests='+testCount+', suites='+suiteCount+', passed='+passCount+', skipped='+skippedCount+', todo='+todoCount+', failed=0)');\"",
"verify:training-image-modes": "tsx scripts/verify-training-image-modes.ts",
"test:product-state-volume": "node --experimental-strip-types scripts/product-state-volume-integration.test.ts",
"test:boundaries": "vitest run --coverage.enabled=false src/ownership/rootOwnershipBoundary.test.ts src/deployment/providerRuntimeBoundary.test.ts src/ownership/mnemePublicImportBoundary.test.ts src/target/containerBundleArchive.test.ts",
"test:causal-conformance": "tsx src/ledger/causalConformanceCli.ts",
"typecheck": "tsc --project tsconfig.json --noEmit && tsc --project scripts/tsconfig.json",
"test:e2e:distribution-image": "tsx src/e2e/cli.ts distribution-image",
"test:e2e:distribution-roundtrip": "tsx src/e2e/cli.ts distribution-roundtrip",
"test:e2e:daimon-org": "tsx src/e2e/cli.ts daimon-org",
"test:scripts": "node --experimental-strip-types --test scripts/build-local-daimon-runtime.test.ts scripts/build-local-moltnet.test.ts scripts/compile-explicit-test-mcp.test.ts scripts/native-helper-workflows.test.ts scripts/typescript-policy.test.ts",
"test:live:grok-lean-worker": "node --experimental-strip-types ./scripts/grok-lean-worker-live-check.ts",
"test:scripts": "node --experimental-strip-types --test scripts/build-local-daimon-runtime.test.ts scripts/grok-lean-worker-live-check.test.ts scripts/build-local-moltnet.test.ts scripts/compile-explicit-test-mcp.test.ts scripts/native-helper-workflows.test.ts scripts/typescript-policy.test.ts scripts/training-image-modes.test.ts",
"test:unit": "npm run test:vitest; vitest_status=$?; npm run test:verdict; verdict_status=$?; npm run test:coverage-verdict; coverage_status=$?; npm run test:node; node_status=$?; failed_lanes=''; if [ \"$verdict_status\" -eq 2 ]; then failed_lanes='vitest test failures'; fi; if [ \"$node_status\" -eq 2 ]; then if [ -n \"$failed_lanes\" ]; then failed_lanes=\"$failed_lanes; node:test suite\"; else failed_lanes='node:test suite'; fi; fi; result_status=0; if [ -n \"$failed_lanes\" ]; then echo \"FAIL(tests): $failed_lanes\"; result_status=1; fi; if [ \"$verdict_status\" -ne 0 ] && [ \"$verdict_status\" -ne 2 ]; then echo 'FAIL(vitest-verdict): could not determine the vitest result'; result_status=1; fi; if [ \"$node_status\" -ne 0 ] && [ \"$node_status\" -ne 2 ]; then echo 'FAIL(node-lane): could not determine the node:test result'; result_status=1; fi; if [ \"$result_status\" -ne 0 ]; then exit 1; elif [ \"$vitest_status\" -ne 0 ]; then echo \"FAIL(vitest): exited $vitest_status with no test failure\"; exit 1; elif [ \"$coverage_status\" -eq 2 ]; then echo 'FAIL(coverage): thresholds not met (tests all passed)'; exit 2; elif [ \"$coverage_status\" -ne 0 ]; then echo 'FAIL(coverage-verdict): could not evaluate coverage (tests all passed)'; exit 1; else echo 'PASS'; fi",
"test": "npm run test:unit && npm run test:scripts"
},
Expand All @@ -81,5 +84,16 @@
"typescript": "^5.9.3",
"vitest": "^3.2.4"
},
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
"exports": {
"./auth": {
"types": "./dist/auth/index.d.ts",
"import": "./dist/auth/index.js"
},
"./*": "./*",
"./training": {
"types": "./dist/compiler/training/index.d.ts",
"import": "./dist/compiler/training/index.js"
}
}
}
8 changes: 6 additions & 2 deletions runtime-images/daimon/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,14 @@ RUN test -n "${GROK_CLI_VERSION}" \
&& 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 \
amd64) broker_arch=x64; broker_sha=e3fe2738fc8a979861085b4003bf2d5d7c284874897cb6ec2e2e2383211768bd ;; \
arm64) broker_arch=arm64; broker_sha=ad44e02c38e6a3207ac4a3d5fd98b6d2e55341ce42dfd2f07204bbe54a7a653d ;; \
amd64) broker_arch=x64 ;; \
arm64) broker_arch=arm64 ;; \
*) echo "Unsupported Daimon engine-broker architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac \
&& package_manifest=${RUNTIME_ROOT}/node_modules/@noopolis/daimon/dist/runtime/contract-manifest.json \
&& test "$(sha256sum "${package_manifest}" | awk '{print "sha256:" $1}')" = "${DAIMON_MANIFEST_SHA256}" \
&& broker_sha="$(node -e 'const m=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).grokEngineBroker; const v=m?.artifacts?.[process.argv[2]+"Sha256"]; if(typeof v!=="string"||!/^[a-f0-9]{64}$/.test(v))process.exit(1); process.stdout.write(v)' "${package_manifest}" "${broker_arch}")" \
&& node -e 'const m=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8")).grokEngineBroker, a=m?.grokCliArtifacts?.[process.argv[2]]; if(!a||m.grokCliVersion!==process.argv[3]||a.url!==process.argv[4]||a.sha256!==process.argv[5].replace(/^sha256:/,"")){console.error("Grok CLI pin does not match the Daimon contract manifest ("+(m?.grokCliVersion)+")");process.exit(1)}' "${package_manifest}" "${broker_arch}" "${GROK_CLI_VERSION}" "${GROK_CLI_URL}" "${GROK_CLI_SHA256}" \
&& broker_source=${RUNTIME_ROOT}/node_modules/@noopolis/daimon/dist/runtime/native/daimon-engine-broker \
&& test -x "${broker_source}" \
&& test "$(sha256sum "${broker_source}" | awk '{print $1}')" = "${broker_sha}" \
Expand Down
56 changes: 56 additions & 0 deletions runtime-images/training/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Training image

Owns the single-container training dependency recipe. Spawnfile launches and
stops the outer container; Paideia supervises experiments and native trial child
processes inside it. No Docker socket or host home is mounted.

Build inputs are explicit package distributions, locked dependency manifests,
verified native executables and an installed integration entrypoint. Credentials,
cases and results are runtime mounts, never image contents. Native runtime and
Python base images must be immutable. Do not download unpinned CLI installers.
The recipe is opt-in during incubation; no published runtime is implied.

## Layer order and modes

Layers run from least to most frequently changing: Python parent copies, the
baked `2000`/`2100`/`2200` Daimon identities (context-independent, so first),
locked Claude/Paideia/Spawnfile/compiler dependencies, the lock-keyed DSPy venv,
one layout RUN, bridge source plus its editable install, then the entrypoints and
distributions. Nothing RUNs after the late COPY layers, so a changed distribution
rebuilds its own COPY layer, every later COPY layer and the label, but no RUN
layer (no npm, pip or recursive chmod work).

The image copies no Grok binary: `spawnfile.training-container.v3` runs the
native parent's pinned `/usr/local/bin/grok` through the broker, and a second
copy could only ever be an unattested build. The fixed uid/gid identities are
baked because that container's root filesystem is read-only, so its entrypoint
cannot write `/etc/passwd` the way the production organization entrypoint does.

In-image modes are unchanged from the former `chmod 0555 train train-broker &&
chmod -R a+rX /opt/training`, and owners stay COPY/RUN defaults (root):

- Staged context (`src/compiler/training/preparation/contextModes.ts`): every
directory gains 0555 (private staging dirs become 0755); every file gains 0444
plus 0111 when any execute bit exists (0600 -> 0644, 0700/0744 -> 0755,
0400 -> 0444); `train` and `train-broker` are exactly 0555. Staged mtimes are
fixed at 2000-01-01T00:00:00Z.
- RUN outputs: a `find ... -exec chmod a+rX` closure that selects only entries the
recursive chmod would change, so closed lower-layer files are never copied up.
It covers `/opt/training` after dependencies and the bridge after `pip -e`. The
integration `node_modules` layout is created after the closure, as before.
- `contextModes.test.ts` runs the rendered closure against real `chmod -R a+rX`
and asserts staged modes, recipe order and symlinks. Build-cache and timing
effects require a Docker-enabled measurement
(`npm run verify:training-image-modes`, evidence in `.runtime/grok-p5/build/`).

## Broker contract paths

The layout RUN also installs the native parent's own pinned Grok and engine
broker at `/usr/local/bin/grok` and `/opt/daimon/bin/daimon-engine-broker`, the
two paths `GROK_ENGINE_BROKER` fixes, exactly as a generated organization image
does (`src/runtime/container.ts`). It introduces no second build — both come
from `/opt/spawnfile/runtime-installs/daimon/bin` — and a parent whose Grok is
not a manifest-pinned 1.0.34 build is refused at slot provisioning and again by
the slot supervisor, by digest. `spawnfile/node_modules/@noopolis/daimon` links
to the same install so the root entrypoint can import Daimon's public
`/runtime` export for the broker projection.
1 change: 1 addition & 0 deletions runtime-images/training/CLAUDE.md
Loading
Loading