Skip to content

feat(prism): enable unattested Lium dispatch for PRISM jobs (T14) - #54

Closed
echobt wants to merge 572 commits into
mainfrom
feat/lium-dispatch
Closed

feat(prism): enable unattested Lium dispatch for PRISM jobs (T14)#54
echobt wants to merge 572 commits into
mainfrom
feat/lium-dispatch

Conversation

@echobt

@echobtechobt commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Make PRISM actually dispatch training/eval jobs to our Lium machines (plan todo #14).

  • queue.py gate already on main via feat(landmine): port prod hotpatch landmines into main #53: bare execution_backend=lium is allowed without constation_bundle (compute-only; not TEE).
  • Tests aligned to that gate: bare lium accepted; old test_lium_without_bundle_rejected removed.
  • Mock dispatch coverage: master bridge + LiumCapacityScheduler.tick provisions via FakeLiumClient (no live billable rentals).
  • Docs: docs/lium-dispatch.md with ops config keys + T15 1M e2e handoff.

How to trigger dispatch

export BASE_LIUM_TRAINING__ENABLED=true
export BASE_LIUM_TRAINING__API_KEY_FILE=/run/secrets/lium_api_key
export BASE_LIUM_TRAINING__SSH_PUBLIC_KEY_FILE=/run/secrets/lium_ssh.pub
# optional worker label (default remains base_gpu)export PRISM_EXECUTION_BACKEND=lium

Master path: try_build_lium_capacity_schedulerbridge_pending_work enqueue → run_once tick → provision when inventory free.

Changes

FileChange
packages/challenges/prism/tests/test_execution_backend_constation_gate.pybare lium ACCEPTED; PrismWorker constructs without bundle
tests/unit/test_lium_dispatch_unattested.pymock capacity dispatch + fail-closed try_build
docs/lium-dispatch.mdconfig + T15 handoff
packages/challenges/prism/config.example.yamlnote lium backend

Test plan

  • pytest gate: 5 passed (test_lium_without_bundle_accepted_unattested, …)
  • pytest dispatch + adjacent: 27 passed
  • Import smoke: bare require_execution_backend("lium") OK
  • Evidence: .omo/evidence/attested-isolated-platform/T14-lium-dispatch/ORCH-VERIFY.txt
  • CI required checks green on this head SHA

Must not

  • No live billable Lium in tests
  • No TEE / constation_ok claims from selecting lium
  • Constation modules retained (elevation path separate)
  • Default execution_backend remains base_gpu

Refs

Summary by CodeRabbit

  • New Features

    • Added support for dispatching Prism GPU training and evaluation workloads to Lium machines in compute-only mode.
    • Added configuration guidance for selecting Lium as the execution backend through an environment-variable override.
    • Added capacity-aware scheduling that queues work when no suitable Lium capacity is available.
  • Documentation

    • Added operational guidance, configuration details, limitations, live handoff steps, and test instructions for Lium dispatch.
  • Bug Fixes

    • Lium execution no longer requires attestation credentials or a full security bundle.

echobt added 30 commits July 2, 2026 12:29
…oute)
Make the master LLM gateway provider-agnostic and config-driven per the
llm-yunwu contract, superseding all deepseek/openrouter/deepseek-v4-pro guidance.
- tokens: add optional source (wire s) + model (wire m) claims to
GatewayTokenClaims/issue/issue_central_gate/verify (backward compatible).
- providers: replace deepseek/openrouter constants with a config-driven
provider registry (build_providers keyed by name; mock+real).
- gateway: single POST /llm/v1/{path} route; resolve provider+model from the
token source via config (default yunwu/claude-opus-4-8); overwrite request
body model + inject provider key; drop _enforce_model/DEEPSEEK_REQUIRED_MODEL;
provider-agnostic build + secret redaction.
- settings: GatewaySettings gains providers registry + default_provider +
default_model + sources; deepseek/openrouter fields removed.
- executor + assignment_coordination: emit BASE_LLM_GATEWAY_URL (={root}/llm/v1)
+ BASE_GATEWAY_TOKEN, stamp source=agent on assignment tokens.
- cli: build service from the yunwu key; mint-central-gate-token gains --source.
- update all platform gateway tests to the new contract (provider_mode=mock).
…l-swarm.sh)
Render the config-driven, provider-agnostic yunwu gateway block into
deploy/swarm/master.yaml + the install-swarm.sh master-config: providers.yunwu
(base_url https://yunwu.ai/v1, api_key_file /run/secrets/yunwu_api_key),
default_provider/default_model (claude-opus-4-8), and per-source routes
(agent/llm_review). install-swarm.sh now creates + mounts the single
base_gateway_yunwu_api_key secret at /run/secrets/yunwu_api_key on the master
proxy (provider_mode=real), keeps the mandatory HMAC gateway_token_secret,
routes both central gates through the master gateway /llm/v1 with the scoped
central-gate token (source=llm_review), and drops all deepseek/openrouter secret
creation + mounts. Update the swarm/secret/deploy unit tests + runbook to expect
the yunwu mount + absence of deepseek/openrouter. No yunwu key value in git.
VAL-LLM-CODE-010
Reframe yunwu branding in deploy/swarm/README.md so the gateway is described
generically: provider-agnostic, with the active provider + model chosen in
master.yaml and injected server-side from the token source claim. Operational
secret identifiers (base_gateway_yunwu_api_key / YUNWU_API_KEY /
/run/secrets/yunwu_api_key) are unchanged and kept where operators need them,
reframed as the configured-provider key. Docs-only; no code/config/prod change.
Remove stale DeepSeek/OpenRouter direct-provider-key prose from README.md and
docs/operations/validator.md. The master gateway resolves provider+model
server-side from the token 'source' claim (agent / llm_review) per master.yaml;
challenge/eval services + validators hold no raw provider key and authenticate
with a scoped gateway token (base_gateway_token, source=llm_review, /llm/v1).
The single provider key lives only on the master gateway at
/run/secrets/yunwu_api_key. Docs-only; no code/config/test changes.
yunwu intermittently closes the connection mid-response
(httpx.RemoteProtocolError) or returns a transient 5xx; the gateway had
no retry so these collapsed to caller-facing 502s. HttpLLMProvider.forward
now wraps the request in a bounded retry loop (default 3 attempts, each on
a FRESH client) with exponential backoff (0.25s then 0.5s), retrying the
enumerated transient transport exceptions and 502/503/504. A 429 / other
4xx (and 2xx/3xx) returns immediately and is never retried. Retrying is
safe because the gateway is fully buffered (no partial bytes reach the
caller before an attempt succeeds). Retry logs name only the exception
class + attempt counters, so no injected provider key can leak. Retry
policy is configurable via ProviderConfig with production-safe defaults.
Allow the frontend origins (joinbase.ai, www, localhost dev ports and
platform Vercel previews) to call the single public proxy cross-origin.
Public, cookie-less reads only: GET/HEAD/OPTIONS, allow_credentials=False,
so token-gated /v1/admin/* and signed uploads stay unchanged. Middleware
wraps the whole ASGI app, covering forwarded /challenges/* (incl. SSE) and
the included admin/registry routes. Origins overridable via
allowed_cors_origins.
The agent-challenge own_runner reads task definitions only from node-local
named volumes and fails closed on any digest mismatch; the runner image does
not bake the ~89 task trees. Previously the volumes were empty after a fresh
deploy, so every terminal-bench evaluation failed with terminal_bench_failed.
- Add download-terminal-bench-cache.sh: reads the byte-exact public source
(repo + revision + task count) from the frozen golden/dataset-digest.json,
clones at the pinned commit, and stages the task dirs (stripping only the
task-root .gitignore, as harbor does at packaging time) as a --source dir.
- Wire provision_agent_challenge_cache() into install-swarm.sh (STEP 9b, after
deploy_master, before deploy_challenges): download then
acquire-agent-challenge-cache.sh --apply to copy + digest-verify onto the
volumes. Both halves honor the script's dry-run/--apply gate.
No secrets are read or required.
Add src/base/compute/ package:
- provider.py: ProviderClient protocol, InstanceSpec (mandatory
max_lifetime_hours/max_price_per_hour bounds), Offer/Instance, typed
ProviderError/CostGuardrailError.
- lium.py: LiumClient over httpx (X-API-Key, base https://lium.io/api).
list_offers price filtering; provision refuses unbounded/over-priced
specs before any network call, always sends bounded termination_hours,
and terminates+verifies on any post-rent failure (try/finally);
idempotent ensure_ssh_key/ensure_template; idempotent terminate;
verify_terminated via GET /pods; stream_logs; watchtower digest;
GET /users/me balance. API key never logged/repr'd/in errors.
50 offline respx unit tests (VAL-PROV-001/003/004/005/011/017/018 +
secret hygiene). Full base gate green: 1316 passed, coverage 89.1%.
…able log level
Batch of tested master/broker hardening (previously staged in the working tree):
- Broker capacity: add `broker_max_concurrent_global` (server-wide cap on total
concurrent broker jobs across all slugs) and route both run paths through a
single `_register_job` choke point that enforces the per-slug + global caps
atomically under the workload-ledger lock (over-limit -> WorkloadCapacityError
-> HTTP 429 docker_quota_exceeded). Default None = unlimited (behavior-preserving).
- WorkloadLedger.register accepts `max_concurrent_global`, checked under the same
lock as insertion so check-and-register stays atomic.
- Broker output bound: add `broker_log_limit_bytes` (default 5MB) and broker job
lifecycle logging (accepted/finished/timed-out/rejected; never logs secrets).
- Observability: configurable `log_level` (case-insensitive, falls back to INFO).
- Submitter + install-swarm deploy wiring and matching unit tests.
Validation: ruff clean, ruff format clean, full unit suite 1234 passed.
Add build_lium_worker_template (Lium CustomTemplateRequest payload) and
build_targon_worker_app (Targon app definition) in src/base/compute/
worker_deployment.py. Both pin the docker image BY DIGEST via a shared
pinned_image_reference helper; the image+digest default to the published
prism-evaluator digest (M1 placeholder) and are inputs so M2 swaps in
docker/Dockerfile.worker with a one-line change.
Well-formedness is enforced (required fields, internal ports incl. 22, env
plumbing, is_private, no embedded secrets) and covered by offline respx tests
that also assert the whole compute suite makes zero real network calls under
respx strict mode and needs no provider credentials (VAL-PROV-009/010/016).
…TESTS
Add scripts/live_lium_e2e.py driving the M1 provider clients against
production: read-only reachability (Lium users/me + executors +
watchtower/digest, Targon inventory + apps) plus one batched Lium rental
cycle (ensure ssh key + template -> rent cheapest suitable executor ->
poll RUNNING -> ssh nvidia-smi -> logs -> DELETE -> verify gone -> record
balance delta). Pod is deleted in a finally on every path; opt-in via
BASE_LIVE_PROVIDER_TESTS=1 so the default suite stays offline.
…gpu shape
- LiumClient.provision rejects sub-1-hour max_lifetime_hours so
termination_hours never truncates to 0 (auto-termination stays on)
- widen post-rent cleanup guard to key off rent success, not pod-id
resolution: a transient GET /pods failure during resolution still
best-effort terminates + verifies the just-rented pod
- normalize Targon Offer.price_per_hour to per-GPU (cost_per_hour / gpu_count)
so the per-GPU max_price cap filters multi-GPU shapes correctly
- default WORKER_GPU_SHAPE 'h100' -> live-valid 'h100-small'
Add the miner-funded GPU worker plane registry (architecture.md sec 3.3),
gated behind compute.worker_plane_enabled:
- alembic 0009: worker_registrations + worker_faults + worker_request_nonces
on the existing chain (0008 -> 0009), no legacy table altered.
- WorkerCoordinationService mirroring validator_coordination patterns:
POST /v1/workers/register (sr25519 miner binding verified against the mock
metagraph, binding-nonce replay protection, no silent cross-owner rebind),
POST /v1/workers/{id}/heartbeat, GET /v1/workers (fleet: status/owner/
provider/last-seen/faults, authenticated-but-not-admin), and
GET /v1/workers/active?hotkey= (admission surface).
- Lifecycle pending -> active -> stale -> retired with
compute.worker_heartbeat_ttl_seconds (default 120); retired is terminal
(no heartbeat resurrection); staleness derived on read + a background pass.
- worker_auth: binding message/verify, signed-request verifier + registered-
worker/validator eligibility, worker nonce store.
- Wired into create_proxy_app + the master proxy CLI (flag-off => unmounted).
Unit + Postgres integration tests (15433) cover VAL-MASTER-001/002/015/016/
018/021 and VAL-AGENT-015.
…x spurious 502s
Broaden RETRYABLE_STATUS_CODES to include Cloudflare 520/522/524, Anthropic 529
(overloaded) and generic 500 so a transient edge/upstream overload is retried
instead of collapsed into an opaque 502. Add full-jitter backoff and a 4th
attempt. Add a true streaming passthrough for stream=true callers (the eval
agent): open the upstream stream with a per-chunk read timeout so a slow large
completion no longer trips Cloudflare 524 or the 30s buffered read cap, retrying
only pre-first-byte. Log the real upstream status (status+source only, never
bodies/keys) so a surfaced failure is no longer an opaque 502. The buffered
llm_reviewer safety-gate path response shape is unchanged.
Add the miner-funded WorkerAgent (src/base/worker/): register under a
miner-signed binding, heartbeat to stay active, pull gpu-only replicas,
execute via the AssignmentExecutor seam on its local broker, and post
results that always carry an ExecutionProof envelope (sr25519 over
sha256('{manifest_sha256}:{unit_id}'), pinned identically to prism).
Extract the shared agent-loop primitives into base/coordination/agent_loop
(BackoffPolicy, is_transient_error, AgentCycleSummary, sleep_until,
backoff_sleep); the validator agent now imports them with behavior unchanged.
Add the master worker assignment plane (worker_assignments table + 0010
migration, worker-authenticated pull/result routes gated on registration
and liveness, never a validator permit) and wire it behind
compute.worker_plane_enabled.
Fulfills VAL-AGENT-002/003/004/005/006/007/008/016/017/018.
Add a top-level `base worker` Typer app distinct from the legacy
`base master worker` Swarm group:
- deploy --provider local starts a miner-funded agent against a local
master and reports it active within 60s.
- deploy --provider lium|targon requires the provider key env (actionable
refusal before any network), bounds offer selection by --max-price
preferring an exact gpu_count executor (next-cheapest fallback), and
never transmits the provider key to the master (pod env excludes it).
- status renders the fleet from GET /v1/workers (signed as the worker key).
Thread startup_commands through InstanceSpec, LiumClient.ensure_template
and build_lium_worker_template (validated metachar-free) per the
live-learned Lium rent constraint; keep docker/Dockerfile.worker's
exec-form entrypoint metachar-free.
Add WorkerSettings, worker/miner keypair resolvers, signed list_workers,
config/worker.example.yaml, and unit tests. All behind
compute.worker_plane_enabled; full base gate green.
Add WorkerAssignmentEngine that materializes gpu work-unit replicas onto
ACTIVE distinct-owner workers behind compute.worker_plane_enabled: R=2 with
self-evaluation exclusion (unit waits under sole-capacity scarcity), graceful
degradation to R=1 with a recorded warning, per-worker gpu concurrency 1, and
per-replica deadline/reassignment bounded by max_attempts. The validator
AssignmentService skips worker-plane capabilities when the flag is on, so
flag-OFF gpu routing to validators stays byte-identical to legacy.
… behind flag
Reconcile replicated gpu worker results (architecture.md 3.3): matching
ExecutionProof.manifest_sha256 forwards exactly one result to the challenge;
divergent hashes dispute the unit (never forwarded, before or after audit) and
create a validator-executor audit unit whose outcome writes worker_faults for
the divergent worker(s), visible in fleet status. Single-replica reporting
terminates deterministically (accept-after-degrade with warning); late/foreign
posts stay rejected with replica state intact. All gated by
compute.worker_plane_enabled (reconciler is None when off).
Legacy AssignmentService.reclaim_stale_assignments treated a null
assigned_validator_hotkey as 'offline validator => reassignable', so under
the worker plane a worker-owned prism PRIMARY (ASSIGNED, null hotkey by
design) churned back to PENDING every MasterOrchestrationDriver.run_once
pass. _assign_pending_in_session already skipped such units via
worker_plane_capabilities; _reclaim_in_session did not. Factor the guard
into AssignmentService._worker_plane_owns and apply it in BOTH paths so a
worker-owned primary is neither assigned nor reclaimed here, while a
genuinely stale validator unit (cpu, or gpu AUDIT with executor_kind=
validator) is still reclaimed. Flag OFF keeps reclaim byte-identical to
legacy.
The static --static-challenges installer path built the agent-challenge api and
worker service env WITHOUT CHALLENGE_TERMINAL_BENCH_LOG_STREAM_URL or
CHALLENGE_DOCKER_BROKER_NETWORK, even though both services are already
multi-homed onto base_jobs_internal for exactly this purpose. Without them the
terminal-bench runner JOB lands on the default bridge network, cannot resolve
challenge-agent-challenge by name, and live task.log streaming silently no-ops.
Add both to the shared ac_eval_env array (applied to api+worker), mirroring the
dynamic seed path (cli_app._agent_challenge_own_runner_env), and lock it in with
a parity regression test.
…budget
The eval task concurrency was hardcoded (broker_max_concurrent_global=30,
CHALLENGE_EVALUATION_CONCURRENCY=15), over-committing a 62 GiB manager. Derive it
at install time from system RAM at a 4 GB/task budget:
EVAL_TASK_CONCURRENCY = max(4, floor((TOTAL_RAM_GB - reserve) / 4)), reserve 10
(-> 13 on the 62 GiB box), with EVAL_RAM_TOTAL_GB / EVAL_RAM_RESERVE_GB /
EVAL_TASK_CONCURRENCY overrides. Render it into both broker_max_concurrent_global
and CHALLENGE_EVALUATION_CONCURRENCY, and pin a durable 2g runner (own_runner
DooD client) memory ceiling. The 4 GB divisor only sizes concurrency; the inner
terminal-bench task container keeps its higher 8 GiB ceiling (low actual usage).
…ollback
The master image-updater fired docker service update --detach and never checked
convergence, so a broken :latest image silently degraded proxy/broker with no
revert, no retry budget, and no alert. Make auto-update durable:
- New retry.py: RetryPolicy (bounded exponential backoff + equal jitter) +
per-target RetryState.
- image_updater.py: record pre-update digest as last-known-good (persisted under
the release root, restart-durable), issue the update WITHOUT --detach, poll
UpdateStatus.State to convergence; on paused/rolled_back/timeout re-pin the
last-known-good digest and schedule exponential backoff; after max_attempts
emit an image_update_failed alert and wait for a new digest (which resets).
- Operator freeze: supervisor.image_update_hold (global) + per-target hold skip a
service entirely so a pinned known-good digest is never overridden.
- settings knobs (max_attempts/backoff base+max/hold), new alert kind, alert hook
wired into the image-updater task.
Allow GET /v1/workers/active to authenticate via the prism<->master bridge
shared bearer (the same token base forwards results to prism with) in addition
to the signed-request path, so prism's live admission check works end-to-end
(VAL-CROSS-004). The full-fleet GET /v1/workers stays signed-request only; the
internal bearer is rejected there. Flag OFF => router unmounted (404) unchanged.
Services were created with no rolling-update policy, so a broken image roll was
left paused/degraded with no auto-rollback. Add a Swarm self-healing policy to
every long-lived first-party service create (postgres, master proxy/broker,
challenge api + workers) in both the dynamic (swarm_backend.py) and static
(install-swarm.sh) paths:
--update-failure-action rollback, --update-monitor 45s,
--update-max-failure-ratio 0 (a task that crashes on start within the window
triggers rollback with no healthcheck needed), --update-order stop-first
(required for singleton services on fixed host ports / per-node volumes),
--rollback-failure-action pause, --rollback best-effort. Add a conservative HTTP
/health container healthcheck (python urllib, 40s start-period) to the proxy,
broker, and challenge api for runtime liveness. Kept in sync across both paths.
…loop service locks
- challenge_image_updater: capture pre-roll digest, roll back to previous
image when restart/health-check fails, per-slug retry backoff, and a
challenge_image_update_failed alert once the retry budget is exhausted.
- self_update: bounded retry on manifest+tarball downloads; retry-before-
blacklist via swap_attempts so a transient boot failure no longer
permanently blacklists a possibly-good version; timing/retry knobs sourced
from settings.supervisor (defaults equal prior module constants).
- service_locks: shared per-service update lock registry handed to both the
image-updater and config-sync so their independent loops never issue
overlapping docker service update on the same shared service (one lock at a
time -> deadlock-free).
- cli_app: DockerRuntimeController.rollback helper for challenge rollback.
CI runs 'mypy src tests' (not covered by the local ruff+pytest gate), which
flagged three type errors introduced across the auto-update hardening:
- self_update manifest _fetch annotated -> Any (json.loads is Any) so the
dict-shaped raw.get access type-checks as it did before the retry refactor.
- _FakeResponse.__exit__ returns None (was bool, which mypy rejects for a
context manager that never suppresses).
- test_swarm_backend replicated/job 'common' kwargs dict typed dict[str, Any]
so the heterogeneous **unpack into SwarmServicePlan type-checks.
echobtand others added 26 commits July 27, 2026 21:50
…e tests
Lock stage defaults (review tdx.small/20GB, eval tdx.xlarge/100GB), disk
bounds/billing, lifecycle budget with disk, provision disk_size emission, and
frozen eval/review compose hashes before any production sizing change.
…faults
Split review (tdx.small/20GB) and eval (tdx.xlarge/100GB) defaults, add disk
bounds and billing helpers, and fold optional disk into projected cost while
keeping CpuShape free of a disk field.
Charge both stages for compute plus disk against the shared $20 money cap and
update the ordered CLI lifecycle fixture for disk-aware totals.
Thread stage disk_size_gb on eval/review deployment plans and send disk_size as
a sibling of compose_file without mutating measured compose documents.
… CLI
Default eval to tdx.xlarge, add --review-disk-size-gb/--eval-disk-size-gb, pass
disks into lifecycle budget checks, and harden offline deploy test doubles.
Record review tdx.small/20GB and eval tdx.xlarge/100GB defaults, disk rate, and
unchanged CPU-only $20 money cap in miner and validator self-deploy docs.
Lock DELETE /cvms/{id} allowlist behavior, 204/404 success, unique app_id
resolution, and CLI teardown without a phala binary before the fix lands.
Allow DELETE /cvms/{id} (204/404 success) and refuse ambiguous app_id
matches when resolving teardown identity without a phala binary.
Route teardown through PhalaCloudClient.delete_cvm, accept optional
--cvm-id/--app-id, and resolve unique app_id matches from GET /cvms.
Assert delete_cvm path, ambiguous app_id refusal, and docs no longer
require a phala binary in default_phala_teardown.
Describe DELETE /cvms/{id} as the primary path and keep manual phala
cvms list/delete strings for VAL-DEPLOY-020 verification.
Check --cvm-id/--app-id before constructing PhalaCloudClient so missing
identity fails closed without requiring credentials.
Mirror review prepare recovery: when eval prepare returns token-less
secret_delivery, cancel the current run and retry so deploy receives a
fresh one-shot capability. Production residual on submission 3 left
attempts 1 and 2 stuck after standalone prepare/retry spent the token.
Never cancel when the token is already present; never invent tokens.
Assert token-present skips cancel/retry, token-absent recovers via
cancel+retry, sticky absence raises RouteClientError, and the
{env_key,token} secret_delivery shape stays fail-closed. Also pin
review deploy fixtures to the production REVIEW_API_BASE_URL.
…UN_TOKEN
Cover the production closed loop where eval deploy injects the one-time
run token into the CVM but never surfaces it for eval result. Assert
--emit-run-token / --token-output handoff, fail-closed without either on
live deploy, dry-run exemption, --output hygiene, and prepare/status
redaction regressions.
Live eval deploy now requires --token-output and/or --emit-run-token so
the host can post via eval result. Always include eval_run_id on success
stdout; write the token only to the secure 0600 file or optional stdout
key. Never put the token in --output, redacted prepare/status, logs, or
exception text. CVM encrypted_env injection is unchanged.
…pends it
The handoff check depends only on argv, but ran after
_obtain_eval_prepare_with_token had already consumed the single
EVAL_RUN_TOKEN delivery. A miner who omitted the flags burnt the token
and then hit the error, reproducing the unrecoverable state the handoff
exists to prevent. Validate the destination before any remote mutation
and cover it with a test asserting eval_prepare is never called.
Lock RED→GREEN contract for plan vs CLI shape mismatch and optional
--expected-measurement rtmr0 pin check before Phala create.
…la create
Name plan and CLI shapes plus vm_shape/instance_type/rtmr0, warn that a
stale allowlist pin only surfaces as a generic key-release denial, and
abort before spend. Optional --expected-measurement compares rtmr0 with
truncated prefixes only.
Document guest emit-only design, token handoff flags, host scrape of
BASE_BENCHMARK_RESULT, eval result posting, teardown, and the shape/pin
footgun so miners can finish a run from the docs alone.
…-sizing
feat(agent-challenge): CVM sizing, token handoff, and Phala eval deploy hardening
…push (#52)
* feat(agent-challenge): add temporary NO_PHALA host-local unattested mode
Opt-in host execution while Phala CVMs are disabled. Results are
explicitly marked unattested and cannot be forged as attested. Attested
path stays byte-identical when the flag is off. Contradiction with
attestation flags fails closed at startup.
* feat(agent-challenge): complete NO_PHALA offline pipeline with weight push
Drive the dual-flag-off analysis+own_runner path under NO_PHALA through
scores and authenticated raw-weight push, with unattested provenance on
completion metadata and CRITICAL push logs. Attested gates stay untouched.
* feat(agent-challenge): route NO_PHALA LLM review via OpenRouter Grok
Add OpenRouterReviewProvider for analyzer LLM review when NO_PHALA is on,
with key resolution (env then opencode auth.json), operator config keys,
and fail-closed cost/error handling. Gateway path stays default when mode
is off. Document embed.env keys and cover selection/parsing with unit tests.
* fix(agent-challenge): load file token and host-local NO_PHALA bench
Production mounts the challenge token via shared_token_file; raw-weight
push now resolves it the same way auth does so the loop does not skip.
NO_PHALA + cli docker backend runs own_runner in-process to avoid nested
DooD path mismatch on master embed. Default epoch_seconds matches master
sealer (360s).
* feat(compute): add lium capacity scheduler
Queue on empty 1-GPU Blackwell inventory instead of terminal-failing
capacity misses so training plane can wait for stock.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(compute): add lium orphan pod terminator
Prefix-owned orphan cleanup guards against leaked paid pods after
interrupted training runs.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(config): add LiumTrainingSettings and prism dispatch variant
Expose training-plane env knobs and prism_dispatch_variant so Lium
wiring and orchestration can fail closed without an API key.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(compute): add lium training wiring factories
Build capacity/orphan/client helpers from LiumTrainingSettings with
fail-closed behavior when credentials are missing.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(compute): extend lium client for training GPU lock
Add for_prism_training surface and training GPU lock helpers used by
the host landmine training plane.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(cli): wire lium training plane into master CLI
Register training-plane settings and factories on the CLI entry path
alongside existing constation wiring.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(constation): extend digest allowlist for training plane
Port host allowlist repository deltas required by Lium training
constation checks.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(master): wire lium training into orchestration
Hook capacity, orphan cleanup, and training client factories into the
master orchestration loop.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(prism): add pod_boot contract validators
Pure validators for SHA/URL/env forbid lists used before Lium pod boot.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(prism): gate checkpoint publish and surface intake status
Add PRISM_CHECKPOINT_UPLOAD_ENABLED gate, public top-prism repo default,
CheckpointPublishError→502 mapping, and last_status/last_error intake
observability from the host landmine ports.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(prism): no-op local queue when worker plane owns GPU
When worker-plane is on, process_next becomes a no-op and container
processing refuses so Lium owns GPU execution.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* feat(docker): default prism worker-plane policy in entrypoint
Port host base-master-entrypoint defaults for worker-plane and
plagiarism LLM env into docker/master-entrypoint.sh.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* style: ruff format/import and mypy fixes for landmine ports
Satisfy CI ruff/format/mypy/prism-checks: format touched modules,
sort queue imports, narrow orphan terminate arg, cast orchestration
request in tests, wrap long entrypoint assertion lines.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
---------
Co-authored-by: echobt <154886644+echobt@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
T14 unattested path: execution_backend=lium no longer requires a
constation bundle at PrismWorker construction. Bundle remains optional
API-compat; remote_provider/local_cpu stay rejected.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Mock FakeLiumClient proves bridge+tick provisions a pod and empty
inventory stays queued with capacity_wait. No live billable rentals.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ops keys for BASE_LIUM_TRAINING__*, worker PRISM_EXECUTION_BACKEND=lium,
fail-closed defaults, and live 1M e2e handoff. Not TEE.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@coderabbitai

coderabbitaiBot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change documents the PRISM-to-Lium dispatch flow, adds a lium backend configuration example, updates tests for una tt ested backend acceptance, and adds mocked scheduler/orchestration coverage for provisioning and capacity waits.

Changes

Lium dispatch

Layer / File(s)Summary
Bare Lium backend acceptance
packages/challenges/prism/config.example.yaml, packages/challenges/prism/tests/test_execution_backend_constation_gate.py
Documents the PRISM_EXECUTION_BACKEND=lium override and verifies that bare lium is accepted without a constation bundle.
Scheduler and orchestration validation
tests/unit/test_lium_dispatch_unattested.py, docs/lium-dispatch.md
Tests disabled and fail-closed scheduler construction, mocked pod provisioning, active leases, work assignments, and queued capacity waits; documents configuration, execution flow, and live handoff procedures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly matches the main change: enabling unattested Lium dispatch for PRISM jobs.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lium-dispatch
🔧 Fix failing CI
  • Fix failing CI in branch feat/lium-dispatch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/unit/test_lium_dispatch_unattested.py`:
- Around line 120-124: Fix the Ruff E501 violations in
test_dispatch_enqueue_and_tick_provisions_mocked_pod by wrapping the overlong
docstring or statements at lines 121 and 181–182 to stay within the configured
88-character limit, without changing test behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 854b0c70-5cbb-4403-b787-50db7ae20760

📥 Commits

Reviewing files that changed from the base of the PR and between 32fd012 and 718c424.

📒 Files selected for processing (4)
  • docs/lium-dispatch.md
  • packages/challenges/prism/config.example.yaml
  • packages/challenges/prism/tests/test_execution_backend_constation_gate.py
  • tests/unit/test_lium_dispatch_unattested.py

Comment on lines +120 to +124
async def test_dispatch_enqueue_and_tick_provisions_mocked_pod() -> None:
"""Given Prism pending work + fake Lium inventory, When bridge+tick, Then pod provisioned.

End-to-end master-owned dispatch without constation and without live API.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the Ruff E501 failures.

Lines 121 and 182 exceed the configured 88-character limit, blocking CI.

Proposed fix
- """Given Prism pending work + fake Lium inventory, When bridge+tick, Then pod provisioned.+ """Given pending Prism work and fake Lium inventory, bridge+tick provisions a pod.
...
- """Given empty Lium inventory, When tick, Then lease stays queued (capacity_wait)."""+ """Given empty Lium inventory, tick leaves the lease queued (capacity_wait)."""

Also applies to: 181-182

🧰 Tools
🪛 GitHub Actions: CI / 8_ruff.txt

[error] 121-121: ruff check failed (E501): Line too long (94 > 88).

🪛 GitHub Actions: CI / ruff

[error] 121-121: ruff E501 Line too long (94 > 88).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit/test_lium_dispatch_unattested.py` around lines 120 - 124, Fix the
Ruff E501 violations in test_dispatch_enqueue_and_tick_provisions_mocked_pod by
wrapping the overlong docstring or statements at lines 121 and 181–182 to stay
within the configured 88-character limit, without changing test behavior.

Source: Pipeline failures

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@echobt@alpha1122x