Skip to main content

deepstrike_sdk/
harness.rs

1use async_trait::async_trait;
2
3#[derive(Debug, Clone)]
4pub struct Criterion {
5    pub text: String,
6    pub required: bool,
7    pub weight: f32,
8    /// I3.3 (A4): optional stable id from the host's contract layer; threaded to `VerdictFn`.
9    pub id: Option<String>,
10    /// I3.3 (A4): host hint — host has a deterministic check for this criterion.
11    pub machine_checkable: Option<bool>,
12}
13
14impl Criterion {
15    pub fn required(text: impl Into<String>) -> Self {
16        Self {
17            text: text.into(),
18            required: true,
19            weight: 1.0,
20            id: None,
21            machine_checkable: None,
22        }
23    }
24
25    pub fn optional(text: impl Into<String>) -> Self {
26        Self {
27            text: text.into(),
28            required: false,
29            weight: 1.0,
30            id: None,
31            machine_checkable: None,
32        }
33    }
34
35    pub fn with_weight(mut self, w: f32) -> Self {
36        self.weight = w;
37        self
38    }
39
40    /// I3.3 (A4): attach a stable identifier so `VerdictFn` can dispatch per-criterion checks.
41    pub fn with_id(mut self, id: impl Into<String>) -> Self {
42        self.id = Some(id.into());
43        self
44    }
45
46    /// I3.3 (A4): mark this criterion as host-checkable (deterministic) for `VerdictFn` dispatch.
47    pub fn machine_checkable(mut self, on: bool) -> Self {
48        self.machine_checkable = Some(on);
49        self
50    }
51}
52
53#[derive(Debug, Clone)]
54pub struct CriterionResult {
55    pub criterion: String,
56    pub passed: bool,
57    pub score: f32,
58    pub feedback: String,
59}
60
61#[derive(Debug, Clone)]
62pub struct HarnessRequest {
63    pub goal: String,
64    pub criteria: Vec<Criterion>,
65    pub extensions: Option<serde_json::Value>,
66}
67
68impl HarnessRequest {
69    pub fn new(goal: impl Into<String>) -> Self {
70        Self {
71            goal: goal.into(),
72            criteria: Vec::new(),
73            extensions: None,
74        }
75    }
76}
77
78#[derive(Debug, Clone)]
79pub struct HarnessOutcome {
80    pub result: String,
81    pub passed: bool,
82    pub iterations: u32,
83    pub total_tokens: u64,
84    pub status: String,
85    pub overall_score: f32,
86    pub feedback: Option<String>,
87    pub details: Vec<CriterionResult>,
88}
89
90#[derive(Debug, Clone)]
91pub struct Verdict {
92    pub passed: bool,
93    pub overall_score: f32,
94    pub feedback: String,
95    pub details: Vec<CriterionResult>,
96}
97
98#[derive(Debug, Clone)]
99pub enum HarnessEvent {
100    Token(String),
101    ToolCall {
102        id: String,
103        name: String,
104    },
105    ToolResult {
106        call_id: String,
107        content: String,
108        is_error: bool,
109    },
110    Supervising,
111    Revising {
112        verdict: Verdict,
113    },
114    Done {
115        verdict: Verdict,
116        iterations: u32,
117        total_tokens: u64,
118        status: String,
119    },
120    MaxAttemptsReached,
121}
122
123#[async_trait]
124pub trait QualityGate: Send + Sync {
125    async fn evaluate(
126        &self,
127        request: &HarnessRequest,
128        outcome: &HarnessOutcome,
129    ) -> crate::Result<bool>;
130}
131
132#[async_trait]
133pub trait Harness: Send + Sync {
134    async fn run(&self, request: HarnessRequest) -> crate::Result<HarnessOutcome>;
135}