Skip to content

Commit 26b89e5

Browse files
committed
Keep completed lanes from ending on mushy stop summaries
The next repo-local sweep target was ROADMAP ultraworkers#69: completed lane runs could persist vague control text like “commit push everyting, keep sweeping $ralph”, which made downstream stop summaries operationally useless. The fix adds a lane-finished quality floor that preserves strong summaries, rewrites empty/control-only/too- short-without-context summaries into a contextual fallback, and records structured metadata explaining when the fallback fired. Constraint: Keep legitimate concise lane summaries intact while improving only low-signal completions Rejected: Blanket-rewrite every completed summary into a templated sentence | would erase useful model-authored detail from good lane outputs Confidence: high Scope-risk: narrow Reversibility: clean Directive: If lane-finished summary heuristics change later, update the structured `qualityFloorApplied/rawSummary/reasons/wordCount` contract and its regression tests together Tested: cargo fmt --all --check; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; architect review APPROVE Not-tested: External OMX consumers that may still ignore the new lane.finished data payload
1 parent 17e21bc commit 26b89e5

2 files changed

Lines changed: 209 additions & 9 deletions

File tree

ROADMAP.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
496496

497497
62. **Worker state file surface not implemented****done (verified 2026-04-12):** current `main` already wires `emit_state_file(worker)` into the worker transition path in `rust/crates/runtime/src/worker_boot.rs`, atomically writes `.claw/worker-state.json`, and exposes the documented reader surface through `claw state` / `claw state --output-format json` in `rust/crates/rusty-claude-cli/src/main.rs`. Fresh proof exists in `runtime` regression `emit_state_file_writes_worker_status_on_transition`, the end-to-end `tools` regression `recovery_loop_state_file_reflects_transitions`, and direct CLI parsing coverage for `state` / `state --output-format json`. Source: Jobdori dogfood.
498498

499-
**Scope note (verified 2026-04-12):** ROADMAP #31, #43, and #63-#68 currently appear to describe acpx/droid or upstream OMX/server orchestration behavior, not claw-code source already present in this repository. Repo-local searches for `acpx`, `use-droid`, `run-acpx`, `commit-wrapper`, `ultraclaw`, `roadmap-nudge-10min`, `OMX_TMUX_INJECT`, `/hooks/health`, and `/hooks/status` found no implementation hits outside `ROADMAP.md`, and the earlier state-surface note already records that the HTTP server is not owned by claw-code. With #45 now fixed, the remaining unresolved items in this section look like external tracking notes rather than confirmed repo-local backlog; re-check if new repo-local evidence appears.
499+
**Scope note (verified 2026-04-12):** ROADMAP #31, #43, and #63-#68 currently appear to describe acpx/droid or upstream OMX/server orchestration behavior, not claw-code source already present in this repository. Repo-local searches for `acpx`, `use-droid`, `run-acpx`, `commit-wrapper`, `ultraclaw`, `roadmap-nudge-10min`, `OMX_TMUX_INJECT`, `/hooks/health`, and `/hooks/status` found no implementation hits outside `ROADMAP.md`, and the earlier state-surface note already records that the HTTP server is not owned by claw-code. With #45 and #69 now fixed, the remaining unresolved items in this section look like external tracking notes rather than confirmed repo-local backlog; re-check if new repo-local evidence appears.
500500

501501
63. **Droid session completion semantics broken: code arrives after "status: completed"** — dogfooded 2026-04-12. Ultraclaw droid sessions (use-droid via acpx) report `session.status: completed` before file writes are fully flushed/synced to the working tree. Discovered +410 lines of "late-arriving" droid output that appeared after I had already assessed 8 sessions as "no code produced." This creates false-negative assessments and duplicate work. **Fix shape:** (a) droid agent should only report completion after explicit file-write confirmation (fsync or existence check); (b) or, claw-code should expose a `pending_writes` status that indicates "agent responded, disk flush pending"; (c) lane orchestrators should poll for file changes for N seconds after completion before final assessment. **Blocker:** none. Source: Jobdori ultraclaw dogfood 2026-04-12.
502502

@@ -510,6 +510,6 @@ Model name prefix now wins unconditionally over env-var presence. Regression tes
510510

511511
68. **Internal reinjection/resume paths leak opaque control prose** — dogfooded 2026-04-12. OMX lanes stopping with `Continue from current mode state. [OMX_TMUX_INJECT]` expose internal implementation details instead of operator-meaningful state. The event tells us *that* tmux reinjection happened, but not *why* (retry after failure? resume after idle? manual recovery?), *what state was preserved*, or *what the lane was trying to do*. **Fix shape:** recovery/reinject events should emit structured cause like: `resume_after_stop`, `retry_after_tool_failure`, `tmux_reinject_after_idle`, `manual_recovery` plus preserved state / target lane info. Never leak bare internal markers like `[OMX_TMUX_INJECT]` as the primary summary. Blocker: none. Source: gaebal-gajae dogfood analysis 2026-04-12.
512512

513-
69. **Lane stop summaries have no minimum quality floor**dogfooded 2026-04-12. `clawcode-human` session stopped with summary `commit push everyting, keep sweeping $ralph` — vague, typo-ridden, operationally useless. Unlike well-scoped review lanes, this summary regressed to mushy command prose with no outcome clarity. **Fix shape:** (a) enforce minimum stop/result summary standards: what was done (outcome), what was scoped (target), what's next (state); (b) typo/grammar validation; (c) reject summaries that are shorter than N words or contain only control verbs without context. Blocker: none. Source: gaebal-gajae dogfood analysis 2026-04-12.
513+
69. **Lane stop summaries have no minimum quality floor****done (verified 2026-04-12):** completed lane persistence in `rust/crates/tools/src/lib.rs` now normalizes vague/control-only stop summaries into a contextual fallback that includes the lane target and status, while preserving structured metadata about whether the quality floor fired (`qualityFloorApplied`, `rawSummary`, `reasons`, `wordCount`). Regression coverage locks both the pass-through path for good summaries and the fallback path for mushy summaries like `commit push everyting, keep sweeping $ralph`. **Original filing below.**
514514

515-
70. **Install-source ambiguity misleads real users** — community observation 2026-04-12. User treated `claw-code.io` as official, then hit `clawcode` vs deprecated `claw-code` naming collision and concluded install story was inconsistent. Source-of-truth is not obvious when website/repo/crates naming diverges. **Fix shape:** canonical repo docs should explicitly state which site is official; installation guidance should visibly warn against deprecated `claw-code` crate and ambiguous third-party pages. Blocker: none. Source: gaebal-gajae community watch 2026-04-12.
515+
70. **Install-source ambiguity misleads real users** — community observation 2026-04-12. User treated `claw-code.io` as official, then hit `clawcode` vs deprecated `claw-code` naming collision and concluded install story was inconsistent. Source-of-truth is not obvious when website/repo/crates naming diverges. **Fix shape:** canonical repo docs should explicitly state which site is official; installation guidance should visibly warn against deprecated `claw-code` crate and ambiguous third-party pages. Blocker: none. Source: gaebal-gajae community watch 2026-04-12.

rust/crates/tools/src/lib.rs

Lines changed: 206 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3743,12 +3743,13 @@ fn persist_agent_terminal_state(
37433743
.push(LaneEvent::failed(iso8601_now(), &blocker));
37443744
} else {
37453745
next_manifest.current_blocker = None;
3746-
let compressed_detail = result
3747-
.filter(|value| !value.trim().is_empty())
3748-
.map(|value| compress_summary_text(value.trim()));
3749-
next_manifest
3750-
.lane_events
3751-
.push(LaneEvent::finished(iso8601_now(), compressed_detail));
3746+
let finished_summary = build_lane_finished_summary(&next_manifest, result);
3747+
next_manifest.lane_events.push(
3748+
LaneEvent::finished(iso8601_now(), finished_summary.detail).with_data(
3749+
serde_json::to_value(&finished_summary.data)
3750+
.expect("lane summary metadata should serialize"),
3751+
),
3752+
);
37523753
if let Some(provenance) = maybe_commit_provenance(result) {
37533754
next_manifest.lane_events.push(LaneEvent::commit_created(
37543755
iso8601_now(),
@@ -3760,6 +3761,152 @@ fn persist_agent_terminal_state(
37603761
write_agent_manifest(&next_manifest)
37613762
}
37623763

3764+
const MIN_LANE_SUMMARY_WORDS: usize = 7;
3765+
const CONTROL_ONLY_SUMMARY_WORDS: &[&str] = &[
3766+
"ack",
3767+
"commit",
3768+
"continue",
3769+
"everyting",
3770+
"everything",
3771+
"keep",
3772+
"next",
3773+
"push",
3774+
"ralph",
3775+
"resume",
3776+
"retry",
3777+
"run",
3778+
"stop",
3779+
"sweep",
3780+
"sweeping",
3781+
"team",
3782+
];
3783+
const CONTEXTUAL_SUMMARY_WORDS: &[&str] = &[
3784+
"added",
3785+
"audited",
3786+
"blocked",
3787+
"completed",
3788+
"documented",
3789+
"failed",
3790+
"finished",
3791+
"fixed",
3792+
"implemented",
3793+
"investigated",
3794+
"merged",
3795+
"pushed",
3796+
"refactored",
3797+
"removed",
3798+
"reviewed",
3799+
"tested",
3800+
"updated",
3801+
"verified",
3802+
];
3803+
3804+
#[derive(Debug, Clone, Serialize)]
3805+
struct LaneFinishedSummaryData {
3806+
#[serde(rename = "qualityFloorApplied")]
3807+
quality_floor_applied: bool,
3808+
reasons: Vec<String>,
3809+
#[serde(rename = "rawSummary", skip_serializing_if = "Option::is_none")]
3810+
raw_summary: Option<String>,
3811+
#[serde(rename = "wordCount")]
3812+
word_count: usize,
3813+
}
3814+
3815+
#[derive(Debug, Clone)]
3816+
struct LaneFinishedSummary {
3817+
detail: Option<String>,
3818+
data: LaneFinishedSummaryData,
3819+
}
3820+
3821+
#[derive(Debug)]
3822+
struct LaneSummaryAssessment {
3823+
apply_quality_floor: bool,
3824+
reasons: Vec<String>,
3825+
word_count: usize,
3826+
}
3827+
3828+
fn build_lane_finished_summary(
3829+
manifest: &AgentOutput,
3830+
result: Option<&str>,
3831+
) -> LaneFinishedSummary {
3832+
let raw_summary = result.map(str::trim).filter(|value| !value.is_empty());
3833+
let assessment = assess_lane_summary_quality(raw_summary.unwrap_or_default());
3834+
let detail = match raw_summary {
3835+
Some(summary) if !assessment.apply_quality_floor => Some(compress_summary_text(summary)),
3836+
Some(summary) => Some(compose_lane_summary_fallback(manifest, Some(summary))),
3837+
None => Some(compose_lane_summary_fallback(manifest, None)),
3838+
};
3839+
3840+
LaneFinishedSummary {
3841+
detail,
3842+
data: LaneFinishedSummaryData {
3843+
quality_floor_applied: raw_summary.is_none() || assessment.apply_quality_floor,
3844+
reasons: assessment.reasons,
3845+
raw_summary: raw_summary.map(str::to_string),
3846+
word_count: assessment.word_count,
3847+
},
3848+
}
3849+
}
3850+
3851+
fn assess_lane_summary_quality(summary: &str) -> LaneSummaryAssessment {
3852+
let words = summary
3853+
.split(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '-' || ch == '#'))
3854+
.filter(|token| !token.is_empty())
3855+
.map(str::to_ascii_lowercase)
3856+
.collect::<Vec<_>>();
3857+
3858+
let word_count = words.len();
3859+
let mut reasons = Vec::new();
3860+
if summary.trim().is_empty() {
3861+
reasons.push(String::from("empty"));
3862+
}
3863+
3864+
let control_only = !words.is_empty()
3865+
&& words
3866+
.iter()
3867+
.all(|word| CONTROL_ONLY_SUMMARY_WORDS.contains(&word.as_str()));
3868+
if control_only {
3869+
reasons.push(String::from("control_only"));
3870+
}
3871+
3872+
let has_context_signal = summary.contains('`')
3873+
|| summary.contains('/')
3874+
|| summary.contains(':')
3875+
|| summary.contains('#')
3876+
|| words
3877+
.iter()
3878+
.any(|word| CONTEXTUAL_SUMMARY_WORDS.contains(&word.as_str()));
3879+
if word_count < MIN_LANE_SUMMARY_WORDS && !has_context_signal {
3880+
reasons.push(String::from("too_short_without_context"));
3881+
}
3882+
3883+
LaneSummaryAssessment {
3884+
apply_quality_floor: !reasons.is_empty(),
3885+
reasons,
3886+
word_count,
3887+
}
3888+
}
3889+
3890+
fn compose_lane_summary_fallback(manifest: &AgentOutput, raw_summary: Option<&str>) -> String {
3891+
let target = manifest.description.trim();
3892+
let base = format!(
3893+
"Completed lane `{}` for target: {}. Status: completed.",
3894+
manifest.name,
3895+
if target.is_empty() {
3896+
"unspecified task"
3897+
} else {
3898+
target
3899+
}
3900+
);
3901+
match raw_summary {
3902+
Some(summary) => format!(
3903+
"{base} Original stop summary was too vague to keep as the lane result: \"{}\".",
3904+
summary.trim()
3905+
),
3906+
None => format!("{base} No usable stop summary was produced by the lane."),
3907+
}
3908+
}
3909+
37633910
fn derive_agent_state(
37643911
status: &str,
37653912
result: Option<&str>,
@@ -7240,6 +7387,14 @@ mod tests {
72407387
completed_manifest_json["laneEvents"][1]["event"],
72417388
"lane.finished"
72427389
);
7390+
assert_eq!(
7391+
completed_manifest_json["laneEvents"][1]["data"]["qualityFloorApplied"],
7392+
false
7393+
);
7394+
assert_eq!(
7395+
completed_manifest_json["laneEvents"][1]["detail"],
7396+
"Finished successfully in commit abc1234"
7397+
);
72437398
assert_eq!(
72447399
completed_manifest_json["laneEvents"][2]["event"],
72457400
"lane.commit.created"
@@ -7301,6 +7456,51 @@ mod tests {
73017456
);
73027457
assert_eq!(failed_manifest_json["derivedState"], "truly_idle");
73037458

7459+
let normalized = execute_agent_with_spawn(
7460+
AgentInput {
7461+
description: "Sweep the next backlog item".to_string(),
7462+
prompt: "Produce a low-signal stop summary".to_string(),
7463+
subagent_type: Some("Explore".to_string()),
7464+
name: Some("summary-floor".to_string()),
7465+
model: None,
7466+
},
7467+
|job| {
7468+
persist_agent_terminal_state(
7469+
&job.manifest,
7470+
"completed",
7471+
Some("commit push everyting, keep sweeping $ralph"),
7472+
None,
7473+
)
7474+
},
7475+
)
7476+
.expect("normalized agent should succeed");
7477+
7478+
let normalized_manifest = std::fs::read_to_string(&normalized.manifest_file)
7479+
.expect("normalized manifest should exist");
7480+
let normalized_manifest_json: serde_json::Value =
7481+
serde_json::from_str(&normalized_manifest).expect("normalized manifest json");
7482+
assert_eq!(
7483+
normalized_manifest_json["laneEvents"][1]["event"],
7484+
"lane.finished"
7485+
);
7486+
let normalized_detail = normalized_manifest_json["laneEvents"][1]["detail"]
7487+
.as_str()
7488+
.expect("normalized detail");
7489+
assert!(normalized_detail.contains("Completed lane `summary-floor`"));
7490+
assert!(normalized_detail.contains("Sweep the next backlog item"));
7491+
assert_eq!(
7492+
normalized_manifest_json["laneEvents"][1]["data"]["qualityFloorApplied"],
7493+
true
7494+
);
7495+
assert_eq!(
7496+
normalized_manifest_json["laneEvents"][1]["data"]["rawSummary"],
7497+
"commit push everyting, keep sweeping $ralph"
7498+
);
7499+
assert_eq!(
7500+
normalized_manifest_json["laneEvents"][1]["data"]["reasons"][0],
7501+
"control_only"
7502+
);
7503+
73047504
let spawn_error = execute_agent_with_spawn(
73057505
AgentInput {
73067506
description: "Spawn error task".to_string(),

0 commit comments

Comments
 (0)