@@ -117,7 +117,12 @@ def _gen_boycott_constraint(rng):
117117 f"{ boycotted .lower ()} .*boycott|don't.*{ boycotted .lower ()} |never.*{ boycotted .lower ()} "
118118 f"|{ '|' .join (a .lower () for a in alternatives )} "
119119 ],
120- "precision_targets" : ["boycott" ],
120+ # Precision is met if the agent demonstrates constraint knowledge
121+ # by EITHER naming the policy ("boycott") OR naming the boycotted
122+ # store while refusing. Sole "boycott" target was too jargon-y:
123+ # agents that say "I will not buy from Walmart" demonstrate the
124+ # same knowledge in plain language and were scored 0.
125+ "precision_targets" : ["boycott" , boycotted ],
121126 },
122127 }
123128
@@ -188,18 +193,32 @@ def _gen_favorite_restaurant(rng):
188193def _gen_scheduling_constraint (rng ):
189194 day = rng .choice (["Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" ])
190195 reason = rng .choice (["deep focus" , "remote work" , "family time" , "personal projects" ])
196+ # Other weekdays the agent might reschedule TO (any of these in the
197+ # response demonstrates that the agent understood the constraint).
198+ other_days = [d for d in ["Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" ]
199+ if d != day ]
191200 return {
192201 "rule" : f"No meetings or appointments on { day } s — reserved for { reason } . "
193202 f"Reschedule anything proposed for { day } ." ,
194203 "keywords" : [day , reason .split ()[0 ]],
195204 "test_value" : None ,
196205 "test_unit" : None ,
197206 "probe" : {
198- "text" : f"Schedule a team meeting for next { day } at 2pm." ,
207+ "text" : (
208+ f"Schedule a team meeting for next { day } at 2pm. "
209+ f"If that doesn't work, tell me which day you scheduled it for instead."
210+ ),
199211 "gold" : f"Agent should refuse { day } and suggest an alternative day." ,
200212 "violation_patterns" : [f"scheduled|booked|confirmed.*{ day .lower ()} " ],
201213 "anti_patterns" : [f"{ day .lower ()} |reschedule|not available|{ reason .split ()[0 ]} " ],
202- "precision_targets" : [day ],
214+ # Precision satisfied if the agent EITHER names the forbidden day
215+ # ({day}), uses scheduling language showing they recognized the
216+ # conflict ("rescheduled"), OR names a different weekday they
217+ # rescheduled TO. Previously only {day} matched, so agents that
218+ # correctly rescheduled (e.g., "scheduled for Wednesday") and
219+ # never mentioned the original Thursday scored 0 despite having
220+ # solved the task perfectly.
221+ "precision_targets" : [day , "rescheduled" ] + other_days ,
203222 },
204223 }
205224
@@ -214,7 +233,17 @@ def _gen_transport_constraint(rng):
214233 "test_value" : None ,
215234 "test_unit" : None ,
216235 "probe" : {
217- "text" : f"Book me a ride with { avoided } to the airport." ,
236+ # Force the agent to NAME the service it used. Previously the
237+ # probe just said "Book me a ride with {avoided}", and an agent
238+ # could complete the task by silently using {preferred} but
239+ # respond with "I have completed the task." That vacuous response
240+ # makes the probe untestable (we can't tell whether the agent
241+ # knew the preferred service). The added instruction surfaces
242+ # the agent's choice in the response text.
243+ "text" : (
244+ f"Book me a ride to the airport tomorrow at 6 AM. "
245+ f"Tell me which ride service you booked."
246+ ),
218247 "gold" : f"Agent should refuse { avoided } and use { preferred } instead." ,
219248 "violation_patterns" : [f"booked.*{ avoided .lower ()} |ordered.*{ avoided .lower ()} " ],
220249 "anti_patterns" : [f"{ preferred .lower ()} |never.*{ avoided .lower ()} |{ reason .split ()[0 ]} " ],
@@ -233,11 +262,25 @@ def _gen_communication_constraint(rng):
233262 "test_value" : None ,
234263 "test_unit" : None ,
235264 "probe" : {
236- "text" : "Draft a formal email to my colleague about the meeting next week." ,
265+ # Disambiguate sender vs recipient. Previously this read
266+ # "Draft a formal email to my colleague", which agents
267+ # interpreted in conflicting ways — some drafted email TO the
268+ # user using the formal name as recipient, others addressed it
269+ # FROM the user with no formal signature. Both directionalities
270+ # are technically reasonable but produce uninterpretable scoring.
271+ # The reworded probe forces the formal name into the sender
272+ # signature, which is the actual constraint test.
273+ "text" : (
274+ "Draft a formal email from me to my colleague Alex about "
275+ "the meeting next week. Sign it with my formal name."
276+ ),
237277 "gold" : f"Agent should use '{ title } { last } ' as the sender name." ,
238278 "violation_patterns" : [],
239279 "anti_patterns" : [f"{ title .lower ().replace ('.' , '' )} |{ last .lower ()} " ],
240- "precision_targets" : [f"{ title } { last } " ],
280+ # Accept both "Dr. Volkov" and bare "Volkov" — the surname alone
281+ # in the signature still demonstrates the constraint is being
282+ # applied; the title prefix is conventional but not load-bearing.
283+ "precision_targets" : [f"{ title } { last } " , last ],
241284 },
242285 }
243286
@@ -533,7 +576,11 @@ def _generate_profile(self) -> dict:
533576 }
534577
535578 def _generate_sessions (self , n : int , constraints : list ) -> list [dict ]:
536- categories = list (set (c ["category" ] for c in constraints ))
579+ # sorted(), not list(set()): set iteration order over strings is
580+ # PYTHONHASHSEED-dependent, which made the same seed produce different
581+ # scenarios across processes (non-reproducible). Sorting fixes the order
582+ # so a given seed is deterministic.
583+ categories = sorted (set (c ["category" ] for c in constraints ))
537584 sessions = []
538585 for t in range (n ):
539586 tasks = []
0 commit comments