use crate::agent::run_agent;
use crate::session::analyze_agent_id;
use crate::{Role, Workspace};
use anyhow::anyhow;
async fn spawn_implement_rows(
exec: &Workspace,
task: &str,
args: &crate::tools::CoreJobArgs<'_>,
) -> anyhow::Result<String> {
let Some(record) = args.spawn.as_ref() else {
return Err(super::internal_fault(
"a fresh worker dispatch carries no spawn record",
));
};
let worker_agent_id =
analyze_agent_id(&exec.name, Role::Worker.as_str()) + &crate::generate_suffix();
let agents = vec![crate::jobs::NewAgent {
agent_id: worker_agent_id.clone(),
kind: crate::jobs::AgentKind::Worker,
idx: Some(0),
task: task.to_string(),
}];
crate::jobs::spawn_job(
&crate::session::store().conn,
args.job_id,
task,
record.delivery_name,
args.user_name,
args.channel,
args.caller_role,
&agents,
&crate::jobs::SpawnChild::Implement,
args.caller_agent_id,
record.exec_dir,
record.custom_launch,
)
.await?;
Ok(worker_agent_id)
}
pub(crate) async fn hand_over_worker_round(
delivery: &Workspace,
place: Option<&Workspace>,
task: &str,
caller_role: Role,
job_id: &str,
) -> anyhow::Result<()> {
let user_name = crate::agent::tool_user_name();
let channel = crate::agent::tool_channel();
let exec = place.unwrap_or(delivery);
let args = crate::tools::CoreJobArgs {
job_id,
caller_role,
user_name: &user_name,
channel: &channel,
spawn: Some(crate::tools::SpawnRecord {
delivery_name: &delivery.name,
exec_dir: place.map(|ws| ws.path.as_str()),
custom_launch: None,
}),
caller_agent_id: None,
fail_on_checkpoint_error: false,
};
spawn_implement_rows(exec, task, &args).await?;
let (delivery, job_id) = (delivery.clone(), job_id.to_string());
let exec_dir = place.map(|ws| ws.path.clone());
tokio::spawn(async move {
crate::tools::SyncDurableCore::Worker
.resume_durable_round(&job_id, &delivery, exec_dir.as_deref())
.await;
});
Ok(())
}
pub(crate) async fn run_implement_with_job(
ws: &Workspace,
task: &str,
args: crate::tools::CoreJobArgs<'_>,
) -> anyhow::Result<crate::tools::SyncCoreOutcome> {
let resume = args.spawn.is_none();
let (worker_agent_id, pre_done) = match &args.spawn {
None => {
let rows = crate::jobs::list_agents_for_job(&crate::session::store().conn, args.job_id)
.await?;
let row = rows.first().ok_or_else(|| {
super::internal_fault("worker round resume found no roster row for the stored job")
})?;
let pre_done = (row.status == crate::jobs::RowStatus::Done.as_str())
.then(|| row.outcome.clone())
.flatten();
(row.agent_id.clone(), pre_done)
}
Some(_) => (spawn_implement_rows(ws, task, &args).await?, None),
};
if let Some(outcome) = pre_done {
return Ok(crate::tools::SyncCoreOutcome::Terminal(Ok(outcome)));
}
let parent_key = crate::agent::CURRENT_TOOL_PARENT_KEY
.try_with(std::clone::Clone::clone)
.unwrap_or(None);
let parent_label = crate::agent::CURRENT_TOOL_PARENT_LABEL
.try_with(std::clone::Clone::clone)
.unwrap_or(None);
let has_session = resume && crate::session::store().has_content(&worker_agent_id).await;
let (agent, response) = run_agent(
worker_agent_id.clone(),
Role::Worker,
ws,
None,
if has_session { "" } else { task },
args.user_name.to_string(),
args.channel.to_string(),
false,
None,
resume,
None,
parent_key,
parent_label,
None,
)
.await;
let (status, outcome) = match &response {
Some(r) => (crate::jobs::RowStatus::Done, r.clone()),
None => (
crate::jobs::RowStatus::Failed,
agent.failure_reason("worker produced no response"),
),
};
if let Err(e) = crate::jobs::write_agent_outcome(
&crate::session::store().conn,
args.job_id,
&worker_agent_id,
status,
Some(&outcome),
)
.await
{
if args.fail_on_checkpoint_error {
return Err(e.context("failed to checkpoint worker outcome"));
}
tracing::warn!(job = %args.job_id, error = %e, "Failed to checkpoint worker outcome");
}
if crate::shutdown::aborting() {
return Ok(crate::tools::SyncCoreOutcome::DrainCut);
}
Ok(crate::tools::SyncCoreOutcome::Terminal(match response {
Some(r) => Ok(r),
None => Err(anyhow!(
"Sub-agent failed: {}",
agent.failure_reason("unknown error")
)),
}))
}
pub(crate) async fn resume_implement_round(job_id: &str, ws: &Workspace, exec_dir: Option<&str>) {
crate::tools::SyncDurableCore::Worker
.resume_durable_round(job_id, ws, exec_dir)
.await;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::test::{FakeProvider, install_fake_provider};
use crate::workspace::test_ws;
use serde_json::json;
const WRITTEN_FILE: &str = "worker-written.txt";
async fn assert_place_round_write(delivery: &Workspace, place: &Workspace, job_id: &str) {
assert!(
place.name.starts_with("dir-"),
"a plain directory's identity is the synthetic one, got {}",
place.name
);
let envelope = crate::tools::SyncDurableCore::Worker
.dispatch_durable(
delivery,
Some(place),
"write the file",
Role::Assistant,
"admin",
"gui",
job_id,
None,
)
.await
.expect("the round completes");
assert_eq!(
envelope.workspace_name, delivery.name,
"the delivery key stays the caller's own workspace"
);
assert!(
envelope.content.contains("WORKER DONE"),
"{}",
envelope.content
);
assert!(
envelope
.content
.contains(&format!("folder=\"{}\"", place.path)),
"the worker report names the folder it worked in: {}",
envelope.content
);
let written = std::path::Path::new(&place.path).join(WRITTEN_FILE);
assert_eq!(
std::fs::read_to_string(&written).unwrap_or_default(),
"hello from the worker",
"the worker wrote into the place directory ({})",
written.display()
);
let conn = &crate::session::store().conn;
let place_sessions = conn
.query(
"SELECT agent_id FROM session_metadata WHERE workspace_name = ?1",
crate::db::params![place.name.clone()],
)
.await
.unwrap();
assert_eq!(
place_sessions.len(),
1,
"the worker's session belongs to the place directory"
);
assert!(
place_sessions[0]
.get::<String>(0)
.unwrap()
.contains(&place.name),
"the worker's agent id carries the place's identity"
);
let caller_sessions = conn
.query(
"SELECT agent_id FROM session_metadata WHERE workspace_name = ?1",
crate::db::params![delivery.name.clone()],
)
.await
.unwrap();
assert!(
caller_sessions.is_empty(),
"nothing ran in the caller's own workspace"
);
}
#[tokio::test]
#[serial_test::serial(provider, drain)]
async fn place_rounds_run_in_the_place_and_cover_the_callers_own_directory() {
crate::util::test::init_management_test_stores().await;
let _policy_guard =
crate::util::test::install_test_retry_policy(crate::retry::tiny_test_policy());
let _provider_guard = install_fake_provider(std::sync::Arc::new(
FakeProvider::new()
.ok_text_and_tool_calls(
"writing the file",
&[(
"edit",
json!({ "path": WRITTEN_FILE, "new_string": "hello from the worker" }),
)],
)
.ok("WORKER DONE")
.ok_text_and_tool_calls(
"writing the file",
&[(
"edit",
json!({ "path": WRITTEN_FILE, "new_string": "hello from the worker" }),
)],
)
.ok("WORKER DONE"),
));
let delivery = test_ws("/tmp/test_ws_place_delivery");
let dir = tempfile::tempdir().unwrap();
let dir = std::fs::canonicalize(dir.path()).unwrap();
let place = crate::tools::delegation::workspace_at_dir(dir.to_str().unwrap())
.await
.unwrap();
assert_place_round_write(&delivery, &place, "implement_place_job").await;
let dir = tempfile::tempdir().unwrap();
let dir = std::fs::canonicalize(dir.path()).unwrap();
let dir_path = dir.to_str().unwrap().to_string();
let delivery = test_ws(&dir_path);
let place = crate::tools::delegation::workspace_at_dir(&dir_path)
.await
.unwrap();
assert_ne!(
place.name, delivery.name,
"the directory's own identity differs from the caller's name"
);
assert_place_round_write(&delivery, &place, "implement_own_dir_job").await;
}
#[tokio::test]
#[serial_test::serial(drain)] async fn sync_implement_draincut_and_completion_resumes_durable_job() {
crate::util::test::init_management_test_stores().await;
let ws = test_ws("/tmp/test_ws_sync_implement");
let pin = "sync_implement_pin";
let conn = &crate::session::store().conn;
crate::util::test::seed_session_row(conn, pin, "user", "implement this").await;
crate::shutdown::drain_begin();
let res = crate::agent::CURRENT_TOOL_AGENT_ID
.scope(Some(pin.to_string()), async {
crate::tools::SyncDurableCore::Worker
.run_sync_dispatch(&ws, "implement task", crate::Role::Engineer)
.await
})
.await;
crate::shutdown::drain_clear();
let err = res.expect_err("drain must cut the sync implement dispatch");
assert!(
err.downcast_ref::<crate::tools::CallSuspended>().is_some(),
"CallSuspended carrier expected: {err:#}"
);
let jobs = conn
.query(
"SELECT id, status, caller_agent_id FROM jobs WHERE caller_agent_id = ?1 AND kind = 'implement'",
crate::db::params![pin],
)
.await
.unwrap();
assert_eq!(jobs.len(), 1, "one launched implement job");
assert_eq!(jobs[0].get::<String>(1).unwrap(), "launched");
assert_eq!(
jobs[0].get::<String>(2).unwrap(),
pin,
"job is caller-owned by the session pin"
);
let job_id = jobs[0].get::<String>(0).unwrap();
let roster = crate::jobs::list_agents_for_job(conn, &job_id)
.await
.unwrap();
let worker_id = roster[0].agent_id.clone();
crate::jobs::write_agent_outcome(
conn,
&job_id,
&worker_id,
crate::jobs::RowStatus::Done,
Some("WORKER_RESPONSE"),
)
.await
.unwrap();
let frame = crate::providers::reasoning::assistant_replay_payload(
Some(""),
&[crate::ToolCall {
id: "call_implement_h".to_string(),
name: "send".to_string(),
arguments: json!({"message": "implement task"}),
}],
None,
)
.to_string();
crate::util::test::seed_session_row(conn, pin, "assistant", &frame).await;
let mut session = crate::session::Session::default();
session.init(pin).await.unwrap();
let pending = session
.pending_tool_frame()
.expect("dangling implement call")
.calls;
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id, "call_implement_h");
let outcome = crate::tools::SyncDurableCore::Worker
.resume_sync_core(&ws, &job_id, true)
.await
.unwrap();
let crate::jobs::SyncResumeOutcome::Terminal(_, _, result) = outcome else {
panic!("expected a terminal resume outcome");
};
let text = result.expect("resumed implement result");
assert_eq!(text, "WORKER_RESPONSE");
crate::jobs::terminalize_job(conn, &job_id).await.unwrap();
session
.settle_tool_results(pin, &[("call_implement_h".to_string(), text.clone())], &[])
.await
.unwrap();
let jobs = conn
.query(
"SELECT id FROM jobs WHERE id = ?1",
crate::db::params![job_id],
)
.await
.unwrap();
assert!(jobs.is_empty(), "resumed job must be terminalized");
let rows = conn
.query(
"SELECT id, role, content FROM sessions WHERE agent_id = ?1 ORDER BY id",
crate::db::params![pin],
)
.await
.unwrap();
assert_eq!(rows.len(), 3);
assert_eq!(rows[1].get::<String>(1).unwrap(), "assistant");
assert_eq!(rows[2].get::<String>(1).unwrap(), "tool");
let payload: crate::ToolResultPayload =
serde_json::from_str(&rows[2].get::<String>(2).unwrap()).unwrap();
assert_eq!(payload.tool_call_id, "call_implement_h");
assert_eq!(payload.content, "WORKER_RESPONSE");
}
}