made_core/entities/
task_constraints.rs1use serde::{Deserialize, Serialize};
2
3use crate::value_objects::{DurationMs, NumAgents, OutputContract, Rounds, Rubric};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(default)]
8pub struct TaskConstraints {
9 rubric: Rubric,
10 rounds: Rounds,
11 num_agents: Option<NumAgents>,
12 deadline: Option<DurationMs>,
13 output_contract: Option<OutputContract>,
14}
15
16impl TaskConstraints {
17 #[must_use]
18 pub fn new(
19 rubric: Rubric,
20 rounds: Rounds,
21 num_agents: Option<NumAgents>,
22 deadline: Option<DurationMs>,
23 ) -> Self {
24 Self {
25 rubric,
26 rounds,
27 num_agents,
28 deadline,
29 output_contract: None,
30 }
31 }
32
33 #[must_use]
34 pub fn rubric(&self) -> &Rubric {
35 &self.rubric
36 }
37
38 #[must_use]
39 pub fn rounds(&self) -> Rounds {
40 self.rounds
41 }
42
43 #[must_use]
44 pub fn num_agents(&self) -> Option<NumAgents> {
45 self.num_agents
46 }
47
48 #[must_use]
49 pub fn deadline(&self) -> Option<DurationMs> {
50 self.deadline
51 }
52
53 #[must_use]
54 pub fn output_contract(&self) -> Option<&OutputContract> {
55 self.output_contract.as_ref()
56 }
57
58 #[must_use]
59 pub fn with_output_contract(mut self, output_contract: OutputContract) -> Self {
60 self.output_contract = Some(output_contract);
61 self
62 }
63}
64
65impl Default for TaskConstraints {
66 fn default() -> Self {
67 Self {
68 rubric: Rubric::empty(),
69 rounds: Rounds::default(),
70 num_agents: None,
71 deadline: None,
72 output_contract: None,
73 }
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use std::collections::BTreeMap;
80
81 use super::*;
82 use crate::value_objects::{OutputFieldRule, OutputFormat};
83
84 #[test]
85 fn defaults_are_sane() {
86 let constraints = TaskConstraints::default();
87 assert_eq!(constraints.rounds(), Rounds::default());
88 assert!(constraints.num_agents().is_none());
89 assert!(constraints.deadline().is_none());
90 assert!(constraints.rubric().is_empty());
91 }
92
93 #[test]
94 fn accepts_optional_bounds() {
95 let constraints = TaskConstraints::new(
96 Rubric::empty(),
97 Rounds::new(3).unwrap(),
98 Some(NumAgents::new(4).unwrap()),
99 Some(DurationMs::from_millis(1500)),
100 );
101 assert_eq!(constraints.rounds().get(), 3);
102 assert_eq!(constraints.num_agents().unwrap().get(), 4);
103 assert_eq!(constraints.deadline().unwrap().get(), 1500);
104 }
105
106 #[test]
107 fn supports_structured_output_contract() {
108 let contract = OutputContract::new(
109 "decision-contract",
110 OutputFormat::JsonObject,
111 BTreeMap::from([(
112 "decision".to_owned(),
113 OutputFieldRule::new(true, ["emit_event", "escalate"]).unwrap(),
114 )]),
115 )
116 .unwrap();
117
118 let constraints = TaskConstraints::default().with_output_contract(contract.clone());
119 assert_eq!(constraints.output_contract(), Some(&contract));
120 }
121
122 #[test]
123 fn empty_json_object_deserializes_to_defaults() {
124 let constraints: TaskConstraints = serde_json::from_str("{}").unwrap();
125 assert_eq!(constraints, TaskConstraints::default());
126 }
127}