Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 16
fix(prism): stop false stuck-sweep and retain harness log tails#88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -10,9 +10,11 @@ use tokio::time::sleep; | ||
| use tracing::{debug, info, warn}; | ||
| use crate::error::{CostGuardrailError, LiumError}; | ||
| use crate::ssh::{parse_ssh_target, resolve_private_key, ssh_exec, ssh_exec_allow_fail, SshTarget}; | ||
| use crate::ssh::{ | ||
| parse_ssh_target, resolve_private_key, ssh_exec, ssh_exec_allow_fail, truncate_tail, SshTarget, | ||
| }; | ||
| use crate::types::{GpuPreference, Instance, InstanceSpec, LiumSshConfig, Offer, RemoteExecResult}; | ||
| use crate::{EvalJobBackend, LIUM_API_BASE_URL, MIN_LIFETIME_HOURS}; | ||
| use crate::{EvalJobBackend, HARNESS_LOG_RETAIN_BYTES, LIUM_API_BASE_URL, MIN_LIFETIME_HOURS}; | ||
| /// Pod image: Lium-owned DinD variant pulses its own dockerd init and never | ||
| /// Only `daturaai/*-dind` pods deliver a reachable sshd on this marketplace | ||
| @@ -433,12 +435,21 @@ echo '{harness_b64}' | base64 -d > /tmp/prism_eval/prism_harness.py | ||
| echo '{arch_b64}' | base64 -d > /tmp/prism_eval/architecture.py | ||
| echo '{train_b64}' | base64 -d > /tmp/prism_eval/training.py | ||
| cd /tmp/prism_eval | ||
| # Persist full harness output on-pod so timeout / stuck-sweep can harvest the | ||
| # fatal tail even when the long-lived SSH session is killed without pipes. | ||
| set +e | ||
| PRISM_DATASET_URL='{dataset_url}' \ | ||
| PRISM_DATASET_SHA256='{dataset_sha}' \ | ||
| PRISM_MAX_TRAIN_STEPS='{steps}' \ | ||
| PRISM_TRAIN_HOURS_CAP='{train_hours}' \ | ||
| PRISM_GPU_TYPE='{gpu_type}' \ | ||
| {test_env}timeout --kill-after=60 {timeout_secs} python3 prism_harness.py\n", | ||
| {test_env}timeout --kill-after=60 {timeout_secs} python3 prism_harness.py \ | ||
| > /tmp/prism_eval/harness.log 2>&1 | ||
| ec=$? | ||
| set -e | ||
| # Surface the log tail on the SSH channel for the happy path + failure parse. | ||
| tail -c 524288 /tmp/prism_eval/harness.log || true | ||
| exit $ec\n", | ||
| harness_b64 = harness_b64, | ||
| arch_b64 = arch_b64, | ||
| train_b64 = train_b64, | ||
| @@ -451,7 +462,7 @@ PRISM_GPU_TYPE='{gpu_type}' \ | ||
| timeout_secs = train_cap_secs.saturating_add(3600), | ||
| ); | ||
| let out = ssh_exec_allow_fail( | ||
| let out = match ssh_exec_allow_fail( | ||
| &target, | ||
| &key, | ||
| &remote, | ||
| @@ -460,12 +471,36 @@ PRISM_GPU_TYPE='{gpu_type}' \ | ||
| train_cap_secs.saturating_add(3900), | ||
| ) | ||
| .await | ||
| .map_err(|e| LiumError::Exec(format!("harness transport: {e}")))?; | ||
| { | ||
| Ok(o) => o, | ||
| Err(e) => { | ||
| // Session timed out / dropped — second SSH pulls the on-pod log. | ||
| let harvested = self | ||
| .harvest_logs_inner(instance_id) | ||
| .await | ||
| .unwrap_or_default(); | ||
| return Err(LiumError::Exec(format!( | ||
| "harness transport: {e}; harvested: {}", | ||
| truncate_tail(&harvested, HARNESS_LOG_RETAIN_BYTES) | ||
| ))); | ||
| } | ||
| }; | ||
| if !out.stdout.contains("EVAL_OK") { | ||
| let mut detail = out.stdout.clone(); | ||
| if !out.stderr.is_empty() { | ||
| detail.push_str("\n--- stderr ---\n"); | ||
| detail.push_str(&out.stderr); | ||
| } | ||
| if detail.trim().is_empty() { | ||
| detail = self | ||
| .harvest_logs_inner(instance_id) | ||
| .await | ||
| .unwrap_or_default(); | ||
| } | ||
| return Err(LiumError::Exec(format!( | ||
| "harness failed (code {}): {}", | ||
| out.returncode, | ||
| truncate(&out.stderr, 4000) | ||
| truncate_tail(&detail, HARNESS_LOG_RETAIN_BYTES) | ||
Comment on lines
500
to
+503
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bashset -euo pipefail
# Map the store implementation before inspecting its persistence contracts.
ast-grep outline crates/prism-challenge/src --items all
# Inspect all Rust and SQL references to the affected persisted fields.
rg -n -C 5 '\berror_detail\b|prism_submission|prism_stage_event|StatePatch' \
crates bins -g '*.rs' -g '*.sql'# Locate migrations that can constrain the new retained-log size.
fd -t f -e sql .| xargs -r rg -n -C 3 \
'error_detail|prism_submission|prism_stage_event'Repository: BaseIntelligence/base Length of output: 50378 🏁 Script executed: #!/bin/bashset -euo pipefail
echo"## candidate files"
fd -t f '^(client\.rs|lib\.rs|dbprism\.rs|store\.rs|dbs\.rs|api\.rs)$' crates/prism-lium crates/prism-store crates/prism-challenge bins/prism-challenge
echoecho"## prism-lium harness tail and fail path"
ast-grep outline crates/prism-lium/src/client.rs --match fail_terminal --view expanded ||true
rg -n -C 8 'HARNESS_LOG_RETAIL|failure|err|fail_terminal|returncode' crates/prism-lium/src/client.rs crates/prism-lium/src/lib.rs
echoecho"## prism-store db schema/persistence references"
fd -t f -e sql .| xargs -r rg -n -C 4 'CREATE TABLE.*prism_(submission|stage_event)|prism_(submission|stage_event).*CREATE|error_detail|detail'
sed -n '/PrismSubmissionRow/,/impl PrismSubmissionRow/p' crates/prism-store/src/db.rs 2>/dev/null ||true
sed -n '1,260p' crates/prism-store/src/dbprism.rs
echoecho"## prism-challenge API persistence contract"
ast-grep outline crates/prism-challenge/src/api.rs --view expanded ||true
sed -n '/post_retry/,/}/p' crates/prism-challenge/src/api.rs
sed -n '/pub fn get_status/,/pub fn get_events/p' crates/prism-challenge/src/api.rs
rg -n -C 5 'post_retry|get_status|get_events|fail_terminal|apply\(' crates/prism-challenge/src -g '*.rs'Repository: BaseIntelligence/base Length of output: 50377 🏁 Script executed: #!/bin/bashset -euo pipefail
echo"## prism-store db.rs relevant generated/update functions"
sed -n '1,280p' crates/db/src/prism_store.rs
sed -n '280,520p' crates/db/src/prism_store.rs
echoecho"## prism-lium fail_terminal call sites"
rg -n -C 8 'fail_terminal|Err\(LiumError::Exec|LiumError::Exec\(' crates/prism-challenge src crates/prism-lium crates/prism -g '*.rs'echoecho"## prism-store trait/struct definitions"
sed -n '145,210p' crates/prism-store/src/store.rs
sed -n '440,505p' crates/prism-store/src/store.rs
sed -n '980,1040p' crates/prism-store/src/store.rs
echoecho"## relevant API implementation slices"
sed -n '370,400p' crates/prism-challenge/src/api.rs
sed -n '402,432p' crates/prism-challenge/src/api.rsRepository: BaseIntelligence/base Length of output: 27622 🏁 Script executed: #!/bin/bashset -euo pipefail
echo"## orchestrator failure path around LiumClient.measure/eval"
sed -n '216,250p' crates/prism-challenge/src/orchestrator.rs
sed -n '340,380p' crates/prism-challenge/src/orchestrator.rs
sed -n '304,348p' crates/prism-challenge/src/orchestrator.rs
echoecho"## StageEvent and memory store apply implementation"
sed -n '152,166p' crates/prism-store/src/store.rs
sed -n '444,492p' crates/prism-store/src/store.rs
echoecho"## fail_terminal implementations/call chain in orchestrator"
rg -n -C 10 'maybe_auto_retry|fail_terminal|apply\(' crates/prism-challenge/src/orchestrator.rs crates/prism-challenge/src/api.rs crates/prism-challenge/src/lib.rs -g '*.rs'Repository: BaseIntelligence/base Length of output: 34221 Expand the 32 KiB harness diagnostic contract into persistence coverage.
🤖 Prompt for AI Agents | ||
| ))); | ||
| } | ||
| let line = out | ||
| @@ -484,6 +519,17 @@ PRISM_GPU_TYPE='{gpu_type}' \ | ||
| Ok(v) | ||
| } | ||
| /// SSH-fetch the on-pod harness log tail (empty when missing / unreachable). | ||
| async fn harvest_logs_inner(&self, instance_id: &str) -> Result<String, LiumError> { | ||
| let target = self.resolve_ssh_target(instance_id).await?; | ||
| let key = resolve_private_key(self.ssh.private_key_path.as_deref())?; | ||
| let cmd = format!( | ||
| "tail -c {HARNESS_LOG_RETAIN_BYTES} /tmp/prism_eval/harness.log 2>/dev/null || true" | ||
| ); | ||
| let out = ssh_exec_allow_fail(&target, &key, &cmd, 1, self.ssh.ssh_retry_secs, 45).await?; | ||
| Ok(truncate_tail(&out.stdout, HARNESS_LOG_RETAIN_BYTES)) | ||
| } | ||
| async fn gpu_smoke(&self, target: &SshTarget, key: &Path) -> Result<String, LiumError> { | ||
| let smoke = ssh_exec( | ||
| target, | ||
| @@ -843,6 +889,10 @@ impl EvalJobBackend for LiumClient { | ||
| self.exec_eval_live(instance_id, architecture_py, training_py) | ||
| .await | ||
| } | ||
| async fn harvest_logs(&self, instance_id: &str) -> Result<String, LiumError> { | ||
| self.harvest_logs_inner(instance_id).await | ||
| } | ||
| } | ||
| #[cfg(test)] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -59,9 +59,11 @@ stateDiagram-v2 | ||
| ``` | ||
| All transitions are append-only events in `prism_stage_event`; the row state | ||
| lives in `prism_submission`. The sweeper fails rows stuck past the 7h grace | ||
| as `ChallengeInternal`, and `recover_on_boot` cleans pods referenced by | ||
| interrupted rows. | ||
| lives in `prism_submission`. The sweeper fails rows stuck past the **10h** | ||
| grace (aligned above wait-RUNNING + 6h train + SSH margin; a prior 7h grace | ||
| false-positive swept healthy ~7h19m trains) as `ChallengeInternal` after | ||
| harvesting the on-pod harness log tail, and `recover_on_boot` cleans pods | ||
| referenced by interrupted rows. | ||
Comment on lines
+62
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Document the automatic retry branch. The sweeper does not immediately fail every stuck row as As per coding guidelines, “Treat normative documentation—including architecture files, frozen specifications, threat and operator-security documents, completeness status, runbooks, and 🤖 Prompt for AI AgentsSource: Coding guidelines | ||
| Evaluation (Lium / Sim, review, agentic, leaf emit) is **master-only**. | ||
| Validators never run `prism-challenge` — they fetch sealed weights only. | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Do not requeue until pod termination is verified.
If
terminatefails orverify_terminatedreturnsfalse, this code still callsmaybe_auto_retry. A new worker can then provision another pod while the stuck pod continues training. This can duplicate evaluation work and provider billing.Keep the row non-retryable until termination succeeds and verification confirms that the pod is absent. Let a later sweep retry cleanup.
🤖 Prompt for AI Agents