fix(missions): give repo-less container missions the workspace they are promised
Every agent on a research_only mission refused to work, each reporting it was "in Claude Code", had no /mission/repo, and only had Read/Edit/Bash. All three statements were true. The run still recorded completed — 5 turns, 7.4k tokens, 0 artifacts, no error. The machinery is correct when a repo IS bound (verified on a live prod per-mission container: /mission/repo present, all 5 agents pinned). Only the repo-less path was broken, in three layers that disagreed by construction: - sync_in no-oped without a host checkout and copy mode does not bind /mission, so NOTHING created /mission/repo. The microVM tier already creates it, for the stated reason that "the guest needs the workspace to exist before the agent writes into it". Creating it host-side also un-breaks sync_out, equally a no-op before, so work survives across phases instead of being wiped. - pin_agent_workspaces returned Ok after pinning ZERO agents, so the deliberately-fatal guard in mission_orchestrator could never fire. Its error text already described the exact outcome we got. - The prompt advertised ZeroClaw tool names and explicitly denied `bash`, while every executor ends in `claude -p`: microVM passes Read/Edit/Write/Bash/Agent, session passes Read/Edit/Write/Bash, and claude_cli agents get Claude Code's native toolset — ZeroClaw's gating never reaches the subprocess. It was telling agents to use missing tools and avoid present ones. And it went green because mission_outputs logged the failed collect and continued — with the fail-empty rule and the NO-OUTPUT marker both BELOW that continue, so the phase was retried forever and never failed. The retry is now bounded by a grace window off completed_at. Verified end to end: mission completed, agent wrote /mission/repo/research/firecracker_vs_docker.md, collected and registered as a document artifact (6.6 kB of real content). Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
af89020dfd
commit
4dec77ae6d
@@ -249,12 +249,27 @@ fn host_repo(mission_id: uuid::Uuid) -> std::path::PathBuf {
|
||||
|
||||
/// Push the host checkout into the container before a phase runs.
|
||||
///
|
||||
/// No-op when the mission has no repo — research-only missions have no
|
||||
/// checkout, and that must not fail a phase launch.
|
||||
/// A repo-less mission has no checkout to push, but it still needs
|
||||
/// `/mission/repo` to EXIST inside the container: the phase prompt tells the
|
||||
/// agent that is its working directory, `mission_orchestrator` pins every
|
||||
/// claw's `workspace.path` to it, and `mission_outputs` copies it back out to
|
||||
/// register artifacts. This used to return early instead, so none of those three
|
||||
/// were true — the pin resolved to nothing, ZeroClaw fell back to each agent's
|
||||
/// own sandbox, and the agents (correctly) reported they had no such directory
|
||||
/// and refused to work. Creating it empty is what the microVM tier already does,
|
||||
/// for the same reason: see `microvm_executor::inject` ("the guest needs the
|
||||
/// workspace to exist before the agent writes into it").
|
||||
///
|
||||
/// Creating it host-side rather than `mkdir`-ing in the container keeps the copy
|
||||
/// cycle symmetric — `sync_out` unpacks over this same path, so work written by
|
||||
/// one phase survives into the next instead of being wiped by the next
|
||||
/// `sync_in`.
|
||||
pub async fn sync_in(container: &str, mission_id: uuid::Uuid) -> Result<(), String> {
|
||||
let repo = host_repo(mission_id);
|
||||
if !repo.is_dir() {
|
||||
return Ok(());
|
||||
tokio::fs::create_dir_all(&repo)
|
||||
.await
|
||||
.map_err(|e| format!("create empty workspace {}: {e}", repo.display()))?;
|
||||
}
|
||||
let docker = crate::container_exec::connect()?;
|
||||
copy_in(&docker, container, &repo, "repo").await
|
||||
|
||||
Reference in New Issue
Block a user