mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
fix(tui): compact running children in unscoped agent status
One unscoped 'agent status' poll returned 203KB because every agent — including live ones being polled repeatedly — carried its full session projection: launch manifest with the complete child prompt, the worker event ring, and any checkpointed message history (morning-report issue #4). Supervision needs the top-level facts, not the archive. Running children in the unscoped list now drop the snapshot, worker_record, and checkpoint fields and carry compact: true plus a note naming the two full-detail paths; identity, status, usage, follow-up, takeover, artifacts, and verification all stay. Terminal agents keep the full projection — fetching results is the point of a terminal row — and verbose: true restores the old shape everywhere. Single-agent status is unchanged, including its running-unchanged throttle. Verified: subagent suite 516 green including the new compaction regression (compact running row, full terminal row, verbose restore); cargo fmt clean. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
3e1eb5246c
commit
9fa5e04e62
2 files changed
+104
-3
No files matched your search
@@ -6710,11 +6710,41 @@ async fn inspect_agent_from_input(
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
// Unscoped status is a supervision poll, and it used to return the full
|
||||
// projection for every agent — launch manifest, event ring, and any
|
||||
// checkpointed message history included (one observed poll: 203KB).
|
||||
// Running children now compact to their top-level supervision facts
|
||||
// (status, usage, follow-up, verification stay; the heavy snapshot and
|
||||
// worker_record drop). Terminal agents keep the full projection —
|
||||
// fetching results is the point of a terminal row — and `verbose: true`
|
||||
// restores the old shape everywhere.
|
||||
let verbose = input
|
||||
.get("verbose")
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false);
|
||||
let mut projections = Vec::with_capacity(snapshots.len());
|
||||
for (snapshot, worker_record) in snapshots {
|
||||
projections.push(
|
||||
subagent_session_projection(snapshot, include_archived, context, worker_record).await,
|
||||
);
|
||||
let running = snapshot.status == SubAgentStatus::Running;
|
||||
let projection =
|
||||
subagent_session_projection(snapshot, include_archived, context, worker_record).await;
|
||||
let mut value = serde_json::to_value(&projection)
|
||||
.map_err(|err| ToolError::execution_failed(err.to_string()))?;
|
||||
if running
|
||||
&& !verbose
|
||||
&& let Some(object) = value.as_object_mut()
|
||||
{
|
||||
object.remove("snapshot");
|
||||
object.remove("worker_record");
|
||||
object.remove("checkpoint");
|
||||
object.insert("compact".to_string(), json!(true));
|
||||
object.insert(
|
||||
"compact_note".to_string(),
|
||||
json!(
|
||||
"Running child compacted; pass agent_id (or verbose: true) for the full projection."
|
||||
),
|
||||
);
|
||||
}
|
||||
projections.push(value);
|
||||
}
|
||||
let payload = json!({
|
||||
"action": if peek { "peek" } else { "status" },
|
||||
|
||||
@@ -14003,3 +14003,74 @@ fn the_launched_authority_is_the_one_the_spawn_boundary_accepts() {
|
||||
"one member's receipt must not authorize another member's child"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unscoped_status_compacts_running_children_and_keeps_terminal_full() {
|
||||
// Morning-report issue #4: one unscoped status poll returned 203KB
|
||||
// because every RUNNING child carried its full projection (launch
|
||||
// manifest, event ring, checkpoint payloads). Supervision needs the
|
||||
// top-level facts; the heavy fields belong to single-agent status or an
|
||||
// explicit verbose request.
|
||||
let mut inner = SubAgentManager::new(PathBuf::from("."), 1);
|
||||
let current_boot = inner.session_boot_id().to_string();
|
||||
let (input_tx, _input_rx) = mpsc::unbounded_channel();
|
||||
let mut agent = SubAgent::new(
|
||||
"test_agent_running_compact".to_string(),
|
||||
FleetRole::Scout,
|
||||
"prompt".to_string(),
|
||||
make_assignment(),
|
||||
"deepseek-v4-flash".to_string(),
|
||||
Some("Blue".to_string()),
|
||||
Some(vec!["read_file".to_string()]),
|
||||
input_tx,
|
||||
PathBuf::from("."),
|
||||
current_boot,
|
||||
);
|
||||
// A live task handle plus a fresh heartbeat keeps the agent Running.
|
||||
agent.task_handle = Some(tokio::spawn(async {
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
}));
|
||||
inner.agents.insert(agent.id.clone(), agent);
|
||||
|
||||
let manager = Arc::new(RwLock::new(inner));
|
||||
let context = ToolContext::new(".");
|
||||
let result = inspect_agent_from_input(
|
||||
&json!({"action": "status"}),
|
||||
manager.clone(),
|
||||
&context,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("status projection should succeed");
|
||||
let payload: serde_json::Value =
|
||||
serde_json::from_str(&result.content).expect("status payload should be json");
|
||||
let agent_row = payload["agents"]
|
||||
.as_array()
|
||||
.and_then(|agents| agents.first())
|
||||
.expect("running agent row");
|
||||
assert_eq!(agent_row["status"], "running", "{agent_row}");
|
||||
assert_eq!(agent_row["compact"], true, "{agent_row}");
|
||||
assert!(agent_row.get("snapshot").is_none(), "{agent_row}");
|
||||
assert!(agent_row.get("worker_record").is_none(), "{agent_row}");
|
||||
assert!(agent_row["usage"].is_object(), "supervision keeps usage");
|
||||
|
||||
// verbose: true restores the full projection for the same running child.
|
||||
let verbose = inspect_agent_from_input(
|
||||
&json!({"action": "status", "verbose": true}),
|
||||
manager,
|
||||
&context,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("verbose status should succeed");
|
||||
let verbose_payload: serde_json::Value =
|
||||
serde_json::from_str(&verbose.content).expect("verbose payload json");
|
||||
let verbose_row = verbose_payload["agents"]
|
||||
.as_array()
|
||||
.and_then(|agents| agents.first())
|
||||
.expect("verbose agent row");
|
||||
assert!(verbose_row.get("snapshot").is_some(), "{verbose_row}");
|
||||
assert!(verbose_row.get("compact").is_none(), "{verbose_row}");
|
||||
}
|
||||
Reference in new issue
Block a user