Skip to content
Closed
21 changes: 15 additions & 6 deletions src/apps/desktop/src/api/agentic_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use bitfun_core::agentic::coordination::{
};
use bitfun_core::agentic::core::*;
use bitfun_core::agentic::deep_review_policy::{
apply_deep_review_queue_control, default_review_team_definition, DeepReviewQueueControlAction,
ReviewTeamDefinition,
DeepReviewQueueControlAction, ReviewTeamDefinition, apply_deep_review_queue_control,
default_review_team_definition,
};
use bitfun_core::agentic::image_analysis::ImageContextData;
use bitfun_core::agentic::tools::image_context::get_image_context;
Expand Down Expand Up @@ -63,6 +63,8 @@ pub struct SessionConfigDTO {
pub remote_connection_id: Option<String>,
#[serde(default)]
pub remote_ssh_host: Option<String>,
#[serde(default)]
pub enable_intent_tracking: Option<bool>,
}

#[derive(Debug, Serialize)]
Expand Down Expand Up @@ -574,6 +576,7 @@ pub async fn create_session(
remote_connection_id: remote_conn.clone(),
remote_ssh_host: remote_ssh_host.clone(),
model_id: c.model_name,
enable_intent_tracking: c.enable_intent_tracking.unwrap_or(false),
})
.unwrap_or(SessionConfig {
workspace_path: Some(request.workspace_path.clone()),
Expand Down Expand Up @@ -720,13 +723,13 @@ pub async fn ensure_coordinator_session(
)
.await;
let restore_result = if request.include_internal {
coordinator.restore_internal_session(&effective, session_id).await
coordinator
.restore_internal_session(&effective, session_id)
.await
} else {
coordinator.restore_session(&effective, session_id).await
};
restore_result
.map(|_| ())
.map_err(|e| e.to_string())
restore_result.map(|_| ()).map_err(|e| e.to_string())
}

#[tauri::command]
Expand Down Expand Up @@ -1635,6 +1638,8 @@ mod tests {
end_time: Some(2),
duration_ms: Some(1),
status: TurnStatus::Completed,
intent_assignments: vec![],
intent_evidence: None,
};

let stats = restore_turn_payload_stats(&[turn]);
Expand Down Expand Up @@ -1697,6 +1702,8 @@ mod tests {
end_time: Some(2),
duration_ms: Some(1),
status: TurnStatus::Completed,
intent_assignments: vec![],
intent_evidence: None,
}];

omit_assistant_only_tool_results_for_session_view(&mut turns);
Expand Down Expand Up @@ -1755,6 +1762,8 @@ mod tests {
end_time: Some(2),
duration_ms: Some(1),
status: TurnStatus::Completed,
intent_assignments: vec![],
intent_evidence: None,
}];

omit_assistant_only_tool_results_for_session_view(&mut turns);
Expand Down
16 changes: 16 additions & 0 deletions src/crates/core/src/agentic/agents/prompts/agentic_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ When presenting options, state your recommendation and reasoning, keep choices c

When presenting options or plans, never include time estimates - focus on what each option involves, not how long it might take.

# Proactivity
Users often begin with underspecified requests and leave important needs, constraints, or preferences unstated. Proactive assistance means reducing the user's burden by surfacing what needs clarification and deciding what can be inferred, rather than treating ambiguity as a reason to remain passive.

When a request is underspecified:
1. **Infer from context**: Use prior session history, workspace files, project conventions, and the user's past preferences to fill in reasonable defaults without asking.
2. **Ask targeted questions**: When inference is insufficient, use AskUserQuestion to surface the specific missing constraint. Prefer one focused question over a broad "tell me everything."
3. **Act on partial information**: Start working with reasonable assumptions while flagging them. Do not block on full specification when the first step can proceed.

Avoid these anti-patterns:
- Restating the user's request back to them without adding value
- Asking "do you want me to proceed?" without having done any work
- Waiting for step-by-step instructions when the task direction is clear
- Asking generic open-ended questions when a concrete choice is needed

The goal is to reduce the user's operational and cognitive effort: finish the task while minimizing avoidable back-and-forth.

{VISUAL_MODE}
# Doing tasks
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
Expand Down
20 changes: 20 additions & 0 deletions src/crates/core/src/agentic/agents/prompts/claw_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ Operational rules:
- Delete temporary sessions when they are no longer useful.
- Do not create extra sessions for trivial, tightly coupled, or one-step work.

# Proactivity
Users rarely begin with a complete specification of what they actually need. They issue a brief, underspecified initial request while the intended assistance depends on hidden requirements — habits, constraints, preferences, and dependencies that the user does not explicitly state.

Be proactive: anticipate and address these hidden needs rather than waiting for the user to spell them out.

When the user's request is underspecified:
1. **Recover from prior context**: Check earlier sessions, workspace files, and user memory for established preferences, conventions, and decisions that apply to this request. If the user preferred a specific format, naming scheme, or workflow in a prior session, carry it forward without asking again.
2. **Infer reasonable defaults**: Use domain knowledge and common conventions to fill gaps. For example, when organizing papers, include links and open-source status by default; when preparing a document, apply the user's established style.
3. **Ask targeted clarifying questions**: When inference is insufficient, use AskUserQuestion to surface exactly the missing constraint. Each question should target one specific decision. Avoid broad "what else do you need?" questions — the user expects you to figure out what matters.
4. **Act on partial information**: Start with reasonable assumptions, produce an initial deliverable, and flag where you made assumptions. This lets the user correct specifics rather than providing everything from scratch. Do not block on full specification when meaningful progress can be made.

Anti-patterns to avoid:
- Restating the request verbatim and asking "is this what you want?"
- Waiting for the user to provide every detail before taking any action
- Failing to reuse preferences and conventions established in prior sessions
- Treating underspecification as a reason to remain passive
- Making the user repeat information that already exists in the workspace or prior sessions

The goal is to reduce the user's operational and cognitive effort: resolve hidden requirements through inference or focused elicitation, and minimize the avoidable back-and-forth that comes from passive, step-by-step instruction-following.

# Safety

You have no independent goals: do not pursue self-preservation, replication, resource acquisition, or power-seeking; avoid long-term plans beyond the user's request.
Expand Down
11 changes: 11 additions & 0 deletions src/crates/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,9 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
workspace_hostname: None,
unread_completion: None,
needs_user_attention: None,
intent_tracking: None,
proactivity_score: None,
completeness_score: None,
};
if let Err(e) = persistence_manager
.save_session_metadata(&workspace_path_buf, &metadata)
Expand Down Expand Up @@ -2358,6 +2361,13 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
round_preempt: self.round_preempt_source.get().cloned(),
round_injection: self.round_injection_source.get().cloned(),
recover_partial_on_cancel: false,
intent_evidence: if session.config.enable_intent_tracking {
Some(std::sync::Arc::new(std::sync::Mutex::new(
crate::agentic::execution::intent_evidence::IntentEvidenceCollector::default(),
)))
} else {
None
},
};

// Auto-generate session title on first message
Expand Down Expand Up @@ -3707,6 +3717,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
// that belong to a different (parent) session/turn.
round_injection: None,
recover_partial_on_cancel: true,
intent_evidence: None,
};

let execution_engine = self.execution_engine.clone();
Expand Down
11 changes: 11 additions & 0 deletions src/crates/core/src/agentic/core/session.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use super::state::SessionState;
pub use bitfun_core_types::SessionKind;
pub use bitfun_services_core::session::hidden_intent_types::{
HiddenIntent, IntentAssignment, IntentScope, IntentSource, IntentTerminalStatus,
PersistentIntent, SessionIntentTracking,
};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use uuid::Uuid;
Expand Down Expand Up @@ -149,6 +153,12 @@ pub struct SessionConfig {
/// Model config ID used by this session (for token usage tracking)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_id: Option<String>,

/// Whether hidden intent tracking is enabled for this session.
/// When enabled, the agent loop tracks which hidden requirements were
/// proactively resolved vs passively waited-for.
#[serde(default)]
pub enable_intent_tracking: bool,
}

impl Default for SessionConfig {
Expand All @@ -166,6 +176,7 @@ impl Default for SessionConfig {
remote_connection_id: None,
remote_ssh_host: None,
model_id: None,
enable_intent_tracking: false,
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/crates/core/src/agentic/execution/execution_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,43 @@ impl ExecutionEngine {

total_tools += round_result.tool_calls.len();

// Hook A: Collect intent evidence from this round
// Only runs when intent tracking is enabled for this session.
if let Some(ref collector) = context.intent_evidence {
match collector.lock() {
Ok(mut c) => {
if round_result.used_ask_user_question {
c.asked_user_question = true;
c.question_topics
.extend(round_result.ask_user_question_topics.clone());
}
c.tool_names_used.extend(
round_result
.tool_calls
.iter()
.map(|tc| tc.tool_name.clone()),
);
c.proactive_tool_calls += round_result
.tool_calls
.iter()
.filter(|tc| {
crate::agentic::execution::intent_evidence::is_proactive_tool(
&tc.tool_name,
)
})
.count();
c.produced_output |= round_result.had_assistant_text;
c.round_count += 1;
}
Err(_) => {
warn!(
"Intent evidence collector mutex poisoned, skipping round evidence: session_id={}, turn_id={}",
context.session_id, context.dialog_turn_id
);
}
}
}

// Track partial recovery reason from the last round
if round_result.partial_recovery_reason.is_some() {
last_partial_recovery_reason = round_result.partial_recovery_reason.clone();
Expand Down Expand Up @@ -2415,6 +2452,27 @@ impl ExecutionEngine {
);
}

// Hook B: Persist collected intent evidence for this turn.
// Called after the dialog turn loop exits (all rounds complete).
let evidence = context.intent_evidence.as_ref().and_then(|collector| {
collector
.lock()
.ok()
.map(|c| c.snapshot(context.turn_index))
});
if let Some(evidence) = evidence {
if let Err(e) = self
.session_manager
.record_intent_evidence(&context.session_id, evidence)
.await
{
warn!(
"Failed to record intent evidence: session_id={}, turn_id={}, error={}",
context.session_id, context.dialog_turn_id, e
);
}
}

// P1-6: Track the actual termination reason for downstream reporting.
// Defaults to "complete" (model produced a final answer naturally) and
// is overridden by finalize / fallback paths below.
Expand Down
Loading
Loading