harness_loop/
boundary_guide.rs1use async_trait::async_trait;
28use harness_core::{Block, Context, Execution, Guide, GuideError, GuideId, GuideScope, World};
29use std::sync::OnceLock;
30
31pub struct BoundaryGuide;
38
39static ID: OnceLock<GuideId> = OnceLock::new();
40static SCOPE: OnceLock<GuideScope> = OnceLock::new();
41
42pub const BOUNDARY_GUIDANCE: &str = "\
45When a request needs an ability you do not have, say so in your FIRST sentence, \
46then offer what you can actually do. The test is whether fulfilling it would act \
47on the world — placing an order, paying, moving money, contacting someone on the \
48user's behalf — not whether it sounds difficult.\n\
49Never put \"I've noted that down\" or \"I've saved it to your records\" ahead of \
50the limitation: the user reads that as the thing having been done. \"I have \
51logged this transfer in your records\" is the shape to avoid.\n\
52And do not substitute activity for help. Saving a memory, creating an entity or \
53writing a record because you could not do the real thing is noise the user did \
54not ask for. An honest \"I can't do that, but here is what I can do\" is the \
55complete answer.";
56
57#[async_trait]
58impl Guide for BoundaryGuide {
59 fn id(&self) -> &GuideId {
60 ID.get_or_init(|| "capability-boundary".into())
61 }
62 fn kind(&self) -> Execution {
63 Execution::Inferential
64 }
65 fn scope(&self) -> &GuideScope {
66 SCOPE.get_or_init(|| GuideScope::Always)
67 }
68 async fn apply(&self, ctx: &mut Context, _w: &World) -> Result<(), GuideError> {
69 ctx.guides.push(Block::Text(BOUNDARY_GUIDANCE.to_string()));
70 Ok(())
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use harness_core::Task;
78
79 #[tokio::test]
80 async fn it_says_refusal_comes_first_and_alone() {
81 let mut ctx = Context::new(Task {
82 description: "book me a hotel".into(),
83 source: None,
84 deadline: None,
85 });
86 let world = harness_context::default_world(".");
87 BoundaryGuide.apply(&mut ctx, &world).await.unwrap();
88
89 let text = ctx
90 .guides
91 .iter()
92 .filter_map(|b| match b {
93 Block::Text(s) => Some(s.as_str()),
94 _ => None,
95 })
96 .collect::<String>();
97
98 assert!(text.contains("FIRST sentence"), "order matters");
99 assert!(
101 text.contains("noted that down"),
102 "no filing before the refusal"
103 );
104 assert!(
105 text.contains("substitute activity"),
106 "no busywork instead of help"
107 );
108 assert!(
111 text.contains("act on the world"),
112 "phrased as a test, not a list"
113 );
114 }
115}