Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 220 additions & 7 deletions src/openhuman/agent/harness/session/turn/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,112 @@ use std::hash::{Hash, Hasher};
/// persisted `content`, so `seed_resume_from_messages` can't drop it) — that
/// is still a brand-new conversation and should get super context; AND
/// - `enabled` — the `context.super_context_enabled` config flag is on.
/// - `user_message` — the request is not an obvious context-free greeting or
/// simple local action. Super context is useful when prior memory, profile,
/// integrations, or web facts can change the answer; it is counterproductive
/// for "hi"/"ciao" and straightforward local filesystem commands, where an
/// auto-run scout only adds tool noise before the orchestrator sees intent.
///
/// Pulled out as a pure function so the gate (in particular the resume and
/// orchestrator guards) is unit-testable without a full agent turn harness.
fn should_run_super_context(
fn super_context_skip_reason(user_message: &str) -> Option<&'static str> {
let normalized = user_message
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
.trim_matches(|c: char| c.is_ascii_punctuation())
.to_ascii_lowercase();

if normalized.is_empty() {
return Some("empty_message");
}

if matches!(
normalized.as_str(),
"hi" | "hello"
| "hey"
| "yo"
| "ciao"
| "hola"
| "bonjour"
| "thanks"
| "thank you"
| "ok"
| "okay"
| "good morning"
| "good afternoon"
| "good evening"
| "good night"
) {
return Some("context_free_greeting");
}

let local_action_candidate = strip_local_folder_action_lead_in(&normalized);
let starts_like_local_folder_action = [
"create a folder ",
"create folder ",
"make a folder ",
"make folder ",
"create a directory ",
"create directory ",
"make a directory ",
"make directory ",
"mkdir ",
]
.iter()
.any(|prefix| local_action_candidate.starts_with(prefix));

if starts_like_local_folder_action {
let context_hints = [
"discussed",
"mentioned",
"previous",
"earlier",
"last time",
"email",
"gmail",
"calendar",
"slack",
"notion",
"github",
"drive",
];
if !context_hints.iter().any(|hint| normalized.contains(hint)) {
return Some("simple_local_filesystem_action");
}
}

None
}

fn strip_local_folder_action_lead_in(message: &str) -> &str {
let mut candidate = message;
for _ in 0..3 {
let trimmed = candidate.trim_start();
let next = [
"can you please ",
"could you please ",
"would you please ",
"can you ",
"could you ",
"would you ",
"please ",
"hey ",
"hello ",
"hi ",
]
.iter()
.find_map(|lead_in| trimmed.strip_prefix(lead_in));

match next {
Some(rest) => candidate = rest,
None => return trimmed,
}
}
candidate.trim_start()
}

fn super_context_base_gate(
is_orchestrator: bool,
first_turn: bool,
has_prior_conversation: bool,
Expand All @@ -56,6 +158,17 @@ fn should_run_super_context(
is_orchestrator && first_turn && !has_prior_conversation && enabled
}

fn should_run_super_context(
is_orchestrator: bool,
first_turn: bool,
has_prior_conversation: bool,
enabled: bool,
user_message: &str,
) -> bool {
super_context_base_gate(is_orchestrator, first_turn, has_prior_conversation, enabled)
&& super_context_skip_reason(user_message).is_none()
}

// `parse_context_bundle_has_enough_context` moved to
// `tinyagents::middleware` alongside the `SuperContextMiddleware` graph node
// that now owns the first-turn context-collection pass (#4249).
Expand Down Expand Up @@ -606,6 +719,20 @@ impl Agent {
.cached_transcript_messages
.as_ref()
.is_some_and(|msgs| msgs.iter().any(|m| m.role == "assistant"));
let skip_reason_for_logging = super_context_skip_reason(user_message);
let base_gate = super_context_base_gate(
self.agent_definition_id == "orchestrator",
first_turn,
has_prior_conversation,
self.context.super_context_enabled(),
);
if base_gate {
if let Some(reason) = skip_reason_for_logging {
log::info!(
"[agent_loop] super_context skipped for context-free first turn reason={reason}"
);
}
}
// The scout no longer runs here imperatively: super context is now a
// before_model **graph node** (`SuperContextMiddleware`, installed via
// `context_mw.super_context` below). It runs the read-only `context_scout`
Expand All @@ -618,6 +745,7 @@ impl Agent {
first_turn,
has_prior_conversation,
self.context.super_context_enabled(),
user_message,
);
if run_super_context {
log::info!(
Expand Down Expand Up @@ -1283,18 +1411,36 @@ mod super_context_gate_tests {
#[test]
fn runs_only_on_first_turn_of_a_new_orchestrator_thread_when_enabled() {
// Orchestrator, new thread, first turn, flag on → run.
assert!(should_run_super_context(true, true, false, true));
assert!(should_run_super_context(
true,
true,
false,
true,
"find the project we discussed yesterday"
));
}

#[test]
fn skips_when_flag_disabled() {
assert!(!should_run_super_context(true, true, false, false));
assert!(!should_run_super_context(
true,
true,
false,
false,
"find the project we discussed yesterday"
));
}

#[test]
fn skips_on_later_turns() {
// history non-empty → not the first turn.
assert!(!should_run_super_context(true, false, false, true));
assert!(!should_run_super_context(
true,
false,
false,
true,
"find the project we discussed yesterday"
));
}

#[test]
Expand All @@ -1303,7 +1449,13 @@ mod super_context_gate_tests {
// `first_turn` is true) but a seeded prefix that includes a prior
// assistant reply. Super context must NOT re-fire on these existing
// conversations.
assert!(!should_run_super_context(true, true, true, true));
assert!(!should_run_super_context(
true,
true,
true,
true,
"find the project we discussed yesterday"
));
}

#[test]
Expand All @@ -1312,7 +1464,13 @@ mod super_context_gate_tests {
// persisted *user* row (no assistant reply), so `has_prior_conversation`
// is false. That is still a brand-new conversation — super context
// should run.
assert!(should_run_super_context(true, true, false, true));
assert!(should_run_super_context(
true,
true,
false,
true,
"[IMAGE: screenshot.png]\nwhat is this?"
));
}

#[test]
Expand All @@ -1321,7 +1479,62 @@ mod super_context_gate_tests {
// `run_single()` flows (goals enrichment, cron/task agents,
// specialist sub-agents). Even on a fresh first turn with the flag on,
// super context must only run for the user-facing orchestrator.
assert!(!should_run_super_context(false, true, false, true));
assert!(!should_run_super_context(
false,
true,
false,
true,
"find the project we discussed yesterday"
));
}

#[test]
fn skips_context_free_greeting() {
assert!(!should_run_super_context(true, true, false, true, "Ciao"));
assert!(!should_run_super_context(true, true, false, true, "hello!"));
}

#[test]
fn skips_simple_local_folder_creation() {
assert!(!should_run_super_context(
true,
true,
false,
true,
"Create a folder on Desktop named PROVA"
));
}

#[test]
fn skips_simple_local_folder_creation_with_polite_lead_in() {
for message in [
"Please create a folder on Desktop named PROVA",
"Can you make a directory named invoices",
"Hey please create folder screenshots",
] {
assert!(
!should_run_super_context(true, true, false, true, message),
"expected super context to skip for {message:?}"
);
}
}

#[test]
fn keeps_super_context_for_local_action_with_context_hint() {
assert!(should_run_super_context(
true,
true,
false,
true,
"Create a folder for the project we discussed yesterday"
));
assert!(should_run_super_context(
true,
true,
false,
true,
"Please create a folder for the project we discussed yesterday"
));
}

#[test]
Expand Down
Loading