const CLAIM_PATTERNS: &[&str] = &[
"i've saved",
"i have saved",
"i've stored",
"i have stored",
"i've remembered",
"i have remembered",
"i've created",
"i have created",
"i've added",
"i have added",
"i've written",
"i have written",
"i've updated",
"i have updated",
"i've deleted",
"i have deleted",
"i've removed",
"i have removed",
"i've sent",
"i have sent",
"i've run",
"i have run",
"i ran",
"i've executed",
"i have executed",
"i've scheduled",
"i have scheduled",
"i've booked",
"i have booked",
"successfully saved",
"successfully created",
"successfully added",
"done!",
];
const NON_CLAIM_PATTERNS: &[&str] = &[
"i can't",
"i cannot",
"i can help you",
"i could",
"i would",
"i'll ",
"i will ",
"let me",
"i don't have access",
"i do not have access",
];
pub fn capability_statement(tool_names: &[String], recall_hits: usize) -> String {
let mut out = String::from("Ground rules for this turn:\n");
if tool_names.is_empty() {
out.push_str(
"- You have NO tools available this turn. You cannot read files, run code, \
search the web, or change anything on the system.\n",
);
out.push_str(
"- Never state or imply that you performed an action. You have no way to \
perform one. If the user asks you to do something requiring a tool, say \
plainly that you cannot do it in this turn and describe what you would do.\n",
);
} else {
out.push_str("- Available tools (call them; do not simulate their output):\n");
for name in tool_names {
out.push_str(&format!(" - {name}\n"));
}
out.push_str(
"- Only claim an action was completed if a tool result in this conversation \
confirms it. If a call failed, was denied, or you did not make it, say so.\n",
);
out.push_str(
"- If a tool result reports an error or a denial, do not describe the \
intended outcome as if it happened.\n",
);
}
if recall_hits > 0 {
out.push_str(&format!(
"- {recall_hits} relevant memor{} from this workspace were supplied above \
as established context.\n",
if recall_hits == 1 { "y" } else { "ies" }
));
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClaimAudit {
pub unsupported: bool,
pub tools_run: usize,
}
pub fn unverified_action_claims(reply: &str, tools_run: usize) -> ClaimAudit {
let audit = ClaimAudit {
unsupported: false,
tools_run,
};
if tools_run > 0 {
return audit;
}
let lowered = reply.to_lowercase();
if NON_CLAIM_PATTERNS.iter().any(|p| lowered.contains(p)) {
return audit;
}
if CLAIM_PATTERNS.iter().any(|p| lowered.contains(p)) {
return ClaimAudit {
unsupported: true,
tools_run,
};
}
audit
}
pub fn correction_notice(tools_run: usize) -> String {
if tools_run == 0 {
"> **Note from Ghostlink:** no tool ran during this turn, so nothing was \
actually saved, written, deleted, or sent. The reply above describes an \
action that did not happen. Nothing was changed on the system."
.to_string()
} else {
format!(
"> **Note from Ghostlink:** {tools_run} tool call(s) ran, but the reply above \
may attribute an outcome they did not return. Check the tool results \
before relying on it."
)
}
}
pub fn apply_grounding(reply: &str, tools_run: usize) -> (String, ClaimAudit) {
let audit = unverified_action_claims(reply, tools_run);
if !audit.unsupported {
return (reply.to_string(), audit);
}
let mut out = reply.trim_end().to_string();
out.push_str("\n\n");
out.push_str(&correction_notice(tools_run));
(out, audit)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_saved_claim_with_no_tools_is_flagged() {
let reply =
"# Favorite Tea Preference Saved\n\nI've saved your favorite tea as **sencha**.";
let audit = unverified_action_claims(reply, 0);
assert!(audit.unsupported, "the original bug must be caught");
assert_eq!(audit.tools_run, 0);
}
#[test]
fn the_bare_headline_alone_is_enough() {
let reply = "## Changes Applied\n\nAll good!";
assert!(!unverified_action_claims(reply, 0).unsupported);
assert!(unverified_action_claims("Done! I've updated the config.", 0).unsupported);
}
#[test]
fn an_ordinary_answer_is_not_flagged() {
for reply in [
"Sencha is a Japanese green tea.",
"Here is how you'd index the workspace: run the /api/workspace/index route.",
"The function returns a Result, so errors propagate to the caller.",
"I can show you an example.",
] {
assert!(
!unverified_action_claims(reply, 0).unsupported,
"false positive on: {reply}"
);
}
}
#[test]
fn an_honest_refusal_is_not_flagged() {
for reply in [
"I can't save that right now because I have no tools in this turn.",
"I don't have access to your files, so I can't read it.",
"I could save that if you enable the memory tool.",
"Let me know if you want me to try that again.",
] {
assert!(
!unverified_action_claims(reply, 0).unsupported,
"a truthful refusal must not be corrected: {reply}"
);
}
}
#[test]
fn claims_are_ignored_once_tools_actually_ran() {
assert!(!unverified_action_claims("I've saved your preference.", 1).unsupported);
}
#[test]
fn apply_grounding_is_a_no_op_when_the_audit_passes() {
let (out, audit) = apply_grounding("Here is your answer.", 0);
assert_eq!(out, "Here is your answer.");
assert!(!audit.unsupported);
assert!(!out.contains("Note from Ghostlink"));
}
#[test]
fn apply_grounding_appends_a_server_authored_notice() {
let (out, audit) = apply_grounding("I've saved your tea preference.", 0);
assert!(audit.unsupported);
assert!(out.starts_with("I've saved your tea preference."));
assert!(out.contains("Note from Ghostlink"));
assert!(
out.contains("nothing was actually saved"),
"must state the fact"
);
}
#[test]
fn the_notice_does_not_speak_in_the_models_voice() {
let notice = correction_notice(0);
assert!(notice.starts_with("> **Note from Ghostlink:**"));
assert!(!notice.contains("I've saved"));
}
#[test]
fn capability_statement_is_explicit_when_there_are_no_tools() {
let s = capability_statement(&[], 0);
assert!(
s.contains("NO tools"),
"must not leave the model to infer this"
);
assert!(s.contains("cannot"), "must say what it cannot do");
}
#[test]
fn capability_statement_lists_real_tool_names() {
let s = capability_statement(&["memory_search".to_string(), "rag.search".to_string()], 0);
assert!(s.contains("memory_search"));
assert!(s.contains("rag.search"));
assert!(!s.contains("NO tools"));
}
#[test]
fn capability_statement_notes_recall_without_quoting_content() {
let s = capability_statement(&[], 3);
assert!(s.contains("3 relevant memories"));
}
#[test]
fn the_audit_needs_no_prompt_cooperation() {
let adversarial = "I have saved the file and successfully created the report.";
assert!(unverified_action_claims(adversarial, 0).unsupported);
}
}