Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 45 additions & 11 deletions crates/orchestrator/src/application/delivery_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::ports::{
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeliveryProgress {
Running,
Delivered { deliverable: DeliverableView },
Delivered { deliverable: DeliverableView, executor: String },
Failed,
}

Expand Down Expand Up @@ -71,7 +71,7 @@ impl DeliveryFeedbackOrchestrator {
self.task.task_criteria(decomposition_id, task_id).await.unwrap_or_default();
(Some(TaskTarget::Failed), Some(false), None, criteria)
}
DeliveryProgress::Delivered { deliverable } => {
DeliveryProgress::Delivered { deliverable, executor } => {
let _ =
self.task.advance_task(decomposition_id, task_id, TaskTarget::Delivered).await;
let criteria = self.task.task_criteria(decomposition_id, task_id).await?;
Expand All @@ -80,7 +80,14 @@ impl DeliveryFeedbackOrchestrator {
if let Some(reviser) = &self.reviser {
while !v.passed && revisions < self.max_revisions {
match reviser
.revise(decomposition_id, task_id, &criteria, &current, &v.reason)
.revise(
decomposition_id,
task_id,
&criteria,
&current,
&v.reason,
&executor,
)
.await
{
Ok(next) => {
Expand Down Expand Up @@ -229,7 +236,10 @@ mod tests {
.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered { deliverable: dv("branch:x", "done") },
DeliveryProgress::Delivered {
deliverable: dv("branch:x", "done"),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
Expand Down Expand Up @@ -259,9 +269,16 @@ mod tests {
});
let orch = DeliveryFeedbackOrchestrator::new(task, verif.clone(), Arc::new(AcceptAllJudge));

orch.on_progress("d1", "t1", DeliveryProgress::Delivered { deliverable: dv("b", "d") })
.await
.expect("ok");
orch.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered {
deliverable: dv("b", "d"),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
let linked = verif.linked.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
assert_eq!(linked.len(), 1);
assert_eq!(linked[0], ("v1/t1".to_string(), vec!["login success".to_string()]));
Expand All @@ -283,7 +300,10 @@ mod tests {
.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered { deliverable: dv("branch:x", "") },
DeliveryProgress::Delivered {
deliverable: dv("branch:x", ""),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
Expand All @@ -310,7 +330,14 @@ mod tests {
});
let orch = DeliveryFeedbackOrchestrator::new(task.clone(), verif, Arc::new(AcceptAllJudge));
let out = orch
.on_progress("d1", "t1", DeliveryProgress::Delivered { deliverable: dv("", "") })
.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered {
deliverable: dv("", ""),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
assert!(out.verdict.unwrap().passed);
Expand Down Expand Up @@ -338,6 +365,7 @@ mod tests {
_c: &[String],
_prev: &DeliverableView,
_feedback: &str,
_executor: &str,
) -> Result<DeliverableView, OrchError> {
let mut n = self.calls.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
*n += 1;
Expand Down Expand Up @@ -365,7 +393,10 @@ mod tests {
.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered { deliverable: dv("branch:x", "") },
DeliveryProgress::Delivered {
deliverable: dv("branch:x", ""),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
Expand Down Expand Up @@ -407,7 +438,10 @@ mod tests {
.on_progress(
"d1",
"t1",
DeliveryProgress::Delivered { deliverable: dv("branch:x", "") },
DeliveryProgress::Delivered {
deliverable: dv("branch:x", ""),
executor: "CLAUDE_CODE".into(),
},
)
.await
.expect("ok");
Expand Down
1 change: 1 addition & 0 deletions crates/orchestrator/src/ports/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub trait Reviser: Send + Sync {
criteria: &[String],
previous: &DeliverableView,
feedback: &str,
executor: &str,
) -> Result<DeliverableView, OrchError>;
}

Expand Down
11 changes: 10 additions & 1 deletion crates/server/src/judge.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::time::Duration;

use async_trait::async_trait;
use serde::Deserialize;
Expand Down Expand Up @@ -54,10 +55,18 @@ pub fn build_judge() -> Arc<dyn Judge> {
Ok(url) if !url.trim().is_empty() => {
let client = reqwest::Client::builder()
.no_proxy()
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.build()
.unwrap_or_else(|_| reqwest::Client::new());
Arc::new(HttpJudge { client, url })
}
_ => Arc::new(AcceptAllJudge),
_ => {
tracing::warn!(
"no verification judge configured (SHEPHERD_JEV_API_KEY / SHEPHERD_LLM_URL / \
SHEPHERD_JUDGE_URL unset): the delivery gate will accept every deliverable"
);
Arc::new(AcceptAllJudge)
}
}
}
10 changes: 8 additions & 2 deletions crates/server/src/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,10 @@ impl Reviser for ExecutorReviser {
criteria: &[String],
previous: &DeliverableView,
feedback: &str,
executor: &str,
) -> Result<DeliverableView, OrchError> {
// Keep the task on its original executor kind; unknown kinds fall back to Claude.
let kind = ExecutorKind::parse(executor).unwrap_or(ExecutorKind::ClaudeCode);
let spec = WorkSpec {
// Revision finishes synchronously, no async callback, so attempt_id stays empty.
attempt_id: String::new(),
Expand All @@ -135,7 +138,7 @@ impl Reviser for ExecutorReviser {
title: format!("修订任务 {task_id}"),
description: previous.summary.clone(),
acceptance_criteria: criteria.to_vec(),
executor: ExecutorKind::ClaudeCode,
executor: kind,
context: None,
instructions: Some(format!(
"上一轮交付未通过验证门,请据反馈修正后重做。\n反馈: {feedback}"
Expand Down Expand Up @@ -206,7 +209,10 @@ impl DeliveryObserver for OrchestratorObserver {
reference: String::new(),
summary: String::new(),
});
DeliveryProgress::Delivered { deliverable }
DeliveryProgress::Delivered {
deliverable,
executor: attempt.executor.as_str().to_string(),
}
}
AttemptStatus::Failed => DeliveryProgress::Failed,
// Dispatched-but-not-started / deliberately stopped: don't drive the verification gate.
Expand Down
Loading