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:
HmbownandClaude Fable 5 committed 2026-08-02 02:06:14 -07:00
1 parent 3e1eb5246c
commit 9fa5e04e62
2 files changed
+104 -3

No files matched your search

+33 -3
View File
@@ -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" },
+71
View File
@@ -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}");
}