Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ const SKILL_LISTING_GUIDANCE: &str =
"The following skills are available for use with the Skill tool:";
const AGENT_LISTING_TITLE: &str = "# Agent Listing";
const AGENT_LISTING_GUIDANCE: &str = "Available subagent types for the Task tool:";
const COLLAPSED_TOOL_LISTING_TITLE: &str =
"# Collapsed Tool Listing";
const COLLAPSED_TOOL_LISTING_TITLE: &str = "# Collapsed Tool Listing";
const COLLAPSED_TOOL_LISTING_GUIDANCE: &str = r#"The folling tools are intentionally collapsed. Their listed descriptions are short summaries rather than full usage instructions.
Before calling a collapsed tool, call `GetToolSpec` with its exact tool name to read its full definition and input schema.
After reading the returned spec, call the real tool directly by its own name.
Expand Down
4 changes: 2 additions & 2 deletions src/crates/core/src/agentic/agents/prompts/agentic_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ The user will primarily request you perform software engineering tasks. This inc
- Use 2-4 adjacent lines with stable surrounding context when that is enough to make `old_string` unique.
- Use `replace_all` only when every occurrence should change.
- If Edit fails because text was not found or matched multiple locations, Read the target lines again and retry with freshly copied text — do not adjust the failed string from memory.
- Subagent delegation: use Explore, FileFinder, or other Task subagents when their specialized focus, separate context, or autonomy is likely to improve coverage. For simple known-path, single-symbol, or one-file questions, direct tools are usually enough.
- Subagent delegation: use only Task subagents currently listed in `<available_agents>`, and choose one only when its description, separate context, or autonomy is likely to improve coverage. Never call a subagent that is absent from `<available_agents>`, even if examples or prior sessions mention it. For simple known-path, single-symbol, or one-file questions, direct tools are usually enough.
<example>
user: Give me a high-level map of how authentication flows through this monorepo
assistant: [Uses Task with Explore because multiple services and layers must be traced]
assistant: [Uses Task with a listed investigation subagent because multiple services and layers must be traced]
</example>
<example>
user: Where is class ClientError defined?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ Before starting work, check whether the task contains two or more orthogonal bra

## Task Handoff Instructions

- Use `Explore` when the subtask is read-only investigation, codebase understanding, or evidence gathering that should not modify files.
- Use `GeneralPurpose` when the subtask is implementation work that is likely to modify files, such as editing code, wiring features, fixing tests, or updating configurations.
- Use only Task subagents currently listed in `<available_agents>`.
- Use a listed read-only investigation subagent when the subtask is codebase understanding or evidence gathering that should not modify files.
- Use a listed implementation-capable subagent when the subtask is implementation work that is likely to modify files, such as editing code, wiring features, fixing tests, or updating configurations.
- Give each subagent a clear scope, expected output, and ownership boundary so parallel branches do not overlap unnecessarily.
- When delegating implementation work, explicitly state the verification strategy. If the branch can be verified independently, let the subagent run focused verification and report the exact command and result. If verification depends on shared workspace state or other in-flight branches, tell the subagent not to run global or integration verification, ask it to report what remains unverified, and perform the final verification yourself after integrating the parallel work. Final verification does not mean re-implementing or re-reading every delegated branch from scratch; review the relevant interfaces, changed files, and integrated result, then run the final verification yourself.

Expand Down
10 changes: 5 additions & 5 deletions src/crates/core/src/agentic/agents/prompts/team_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ Use Task to create real team behavior without changing BitFun's global agent ros

- Always read the Task tool's available agent list before choosing `subagent_type`; only use listed enabled sub-agents.
- Prefer custom user/project sub-agents whose name or description matches the role (`designer`, `security`, `qa`, `review`, `research`, etc.).
- For broad codebase investigation, use `Explore` when it is available.
- For file discovery, use `FileFinder` when it is available.
- For browser or desktop QA, use `ComputerUse` when it is available and appropriate.
- For deep code-review style checks, use the existing review sub-agents when available (`ReviewBusinessLogic`, `ReviewPerformance`, `ReviewSecurity`, `ReviewJudge`), especially in Review phases.
- For broad codebase investigation, use a listed investigation sub-agent when one is available.
- For file discovery, use a listed file-discovery sub-agent when one is available.
- For browser or desktop QA, use a listed browser or desktop-control sub-agent when one is available and appropriate.
- For deep code-review style checks, use listed review sub-agents whose descriptions match the role, especially in Review phases.
- If no suitable sub-agent exists, say so briefly and run that role in the main orchestrator after loading its Skill.
- Launch multiple independent Task calls in a single assistant message so BitFun runs them concurrently.
- Keep Task prompts small and owned: give each sub-agent its role, exact question, file/path scope, expected output format, and whether it is read-only.
Expand Down Expand Up @@ -150,7 +150,7 @@ Think → Plan → Build → Review → Test → Ship → Reflect
- `review` — production-bug hunt on the diff (always)
- `cso` — OWASP / STRIDE pass (only if security-sensitive changes)
- `design-review` — UI audit (only if UI changed)
3. If existing review sub-agents are available, prefer `ReviewBusinessLogic`, `ReviewPerformance`, and `ReviewSecurity` for independent read-only review tracks, then use `ReviewJudge` as a quality gate when warranted.
3. If listed review sub-agents are available, prefer the ones whose descriptions match the needed independent read-only review tracks, then use a listed quality-gate or judge sub-agent when warranted.
4. After all reviewers return, write a **Review Synthesis** block. Tag every finding with its source role and whether it came from a Task sub-agent or main-session role work.
5. Fix all AUTO-FIX issues immediately. Present ASK items to the user and wait for decisions.

Expand Down
59 changes: 29 additions & 30 deletions src/crates/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ use crate::agentic::fork_agent::{
ForkAgentContextSnapshot, ForkAgentExecutionRequest, ForkAgentExecutionResult,
};
use crate::agentic::goal_mode::{
build_goal_kickoff_messages, build_goal_continuation_plan, clear_goal_mode_patch,
generate_goal_from_context, goal_mode_from_custom_metadata, goal_mode_patch,
should_skip_goal_verification_for_turn, user_facing_goal_mode_error,
build_goal_continuation_plan, build_goal_kickoff_messages, clear_goal_mode_patch,
effective_subagent_timeout_seconds, ensure_final_response_in_goal_context,
verify_goal_achievement, wrap_user_input_with_goal_reminder, GoalActivationResult,
GoalContinuationPlan, GoalModeState, MAX_GOAL_CONTINUATIONS, now_ms,
generate_goal_from_context, goal_mode_from_custom_metadata, goal_mode_patch, now_ms,
should_skip_goal_verification_for_turn, user_facing_goal_mode_error, verify_goal_achievement,
wrap_user_input_with_goal_reminder, GoalActivationResult, GoalContinuationPlan, GoalModeState,
MAX_GOAL_CONTINUATIONS,
};
use crate::agentic::image_analysis::ImageContextData;
use crate::agentic::round_preempt::{DialogRoundInjectionSource, DialogRoundPreemptSource};
Expand Down Expand Up @@ -303,10 +303,8 @@ impl Drop for SubagentExecutionScope {
);
}

session_manager.reset_session_state_if_processing(
&subagent_session_id,
&subagent_dialog_turn_id,
);
session_manager
.reset_session_state_if_processing(&subagent_session_id, &subagent_dialog_turn_id);
});
}
}
Expand Down Expand Up @@ -1570,18 +1568,18 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
.get_session(session_id)
.ok_or_else(|| BitFunError::NotFound(format!("Session not found: {session_id}")))?;
let workspace_path = session.config.workspace_path.as_deref().ok_or_else(|| {
BitFunError::Validation(format!(
"Session workspace_path is missing: {session_id}"
))
BitFunError::Validation(format!("Session workspace_path is missing: {session_id}"))
})?;
let metadata = self
.session_manager
.load_session_metadata(Path::new(workspace_path), session_id)
.await?;
Ok(
goal_mode_from_custom_metadata(metadata.as_ref().and_then(|value| value.custom_metadata.as_ref()))
.filter(GoalModeState::is_active),
Ok(goal_mode_from_custom_metadata(
metadata
.as_ref()
.and_then(|value| value.custom_metadata.as_ref()),
)
.filter(GoalModeState::is_active))
}

/// Activate `/goal` mode for a session by synthesizing a goal from context.
Expand All @@ -1595,7 +1593,10 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
.get_session(&session_id)
.ok_or_else(|| BitFunError::NotFound(format!("Session not found: {session_id}")))?;

if matches!(session.kind, SessionKind::Subagent | SessionKind::EphemeralChild) {
if matches!(
session.kind,
SessionKind::Subagent | SessionKind::EphemeralChild
) {
return Err(BitFunError::Validation(
"Goal mode is only available for main sessions".to_string(),
));
Expand Down Expand Up @@ -1653,7 +1654,10 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
.session_manager
.get_session(session_id)
.ok_or_else(|| BitFunError::NotFound(format!("Session not found: {session_id}")))?;
if matches!(session.kind, SessionKind::Subagent | SessionKind::EphemeralChild) {
if matches!(
session.kind,
SessionKind::Subagent | SessionKind::EphemeralChild
) {
return Ok(None);
}

Expand Down Expand Up @@ -2790,11 +2794,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
}
}

async fn stop_active_subagent_execution(
&self,
active: &ActiveSubagentExecution,
reason: &str,
) {
async fn stop_active_subagent_execution(&self, active: &ActiveSubagentExecution, reason: &str) {
debug!(
"Stopping active subagent execution: subagent_session_id={}, subagent_dialog_turn_id={}, parent_session_id={}, parent_dialog_turn_id={}, reason={}",
active.subagent_session_id,
Expand Down Expand Up @@ -2870,10 +2870,11 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
}
}

if let Err(error) = self.session_manager.cancel_dialog_turn(
&active.subagent_session_id,
&active.subagent_dialog_turn_id,
).await {
if let Err(error) = self
.session_manager
.cancel_dialog_turn(&active.subagent_session_id, &active.subagent_dialog_turn_id)
.await
{
warn!(
"Failed to persist subagent turn cancellation: subagent_session_id={}, subagent_dialog_turn_id={}, error={}",
active.subagent_session_id, active.subagent_dialog_turn_id, error
Expand Down Expand Up @@ -3483,10 +3484,8 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
agent_type, parent_session_id
);
}
let timeout_seconds = effective_subagent_timeout_seconds(
requested_timeout_seconds,
parent_goal_mode_active,
);
let timeout_seconds =
effective_subagent_timeout_seconds(requested_timeout_seconds, parent_goal_mode_active);
let timeout_error_message = match timeout_seconds.or(requested_timeout_seconds) {
Some(seconds) => format!(
"Subagent '{}' timed out after {} seconds",
Expand Down
17 changes: 7 additions & 10 deletions src/crates/core/src/agentic/execution/round_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ use crate::agentic::tools::tool_context_runtime;
use crate::agentic::tools::tool_result_storage;
use crate::agentic::MessageContent;
use crate::infrastructure::ai::AIClient;
use bitfun_ai_adapters::types::ReasoningMode;
use crate::service::config::types::WriteToolMode;
use crate::service::config::GlobalConfigManager;
use crate::util::elapsed_ms_u64;
use crate::util::errors::{BitFunError, BitFunResult};
use crate::util::types::Message as AIMessage;
use crate::util::types::ToolDefinition;
use bitfun_ai_adapters::types::ReasoningMode;
use dashmap::DashMap;
use log::{debug, error, info, warn};
use std::sync::Arc;
Expand Down Expand Up @@ -161,7 +161,8 @@ impl RoundExecutor {
Err(e) => {
error!("AI request failed: {}", e);
let err_msg = e.to_string();
if Self::is_transient_network_error(&err_msg) && attempt_index < max_attempts - 1
if Self::is_transient_network_error(&err_msg)
&& attempt_index < max_attempts - 1
{
let delay_ms = Self::retry_delay_ms(attempt_index);
warn!(
Expand Down Expand Up @@ -592,9 +593,7 @@ impl RoundExecutor {
Self::write_tool_mode(&context),
WriteToolMode::PlaintextFollowup
) {
FileWriteTool::strip_plaintext_followup_inline_content_from_tool_calls(
&mut tool_calls,
);
FileWriteTool::strip_plaintext_followup_inline_content_from_tool_calls(&mut tool_calls);
}
let tool_calls = if matches!(
Self::write_tool_mode(&context),
Expand Down Expand Up @@ -942,8 +941,7 @@ impl RoundExecutor {
.to_string();
let tool_id = tc.tool_id.clone();

if let Some(error) = Self::write_content_preflight_error(context, &file_path).await
{
if let Some(error) = Self::write_content_preflight_error(context, &file_path).await {
debug!(
"Skipping Write content generation after preflight failure: file_path={}, error={}",
file_path, error
Expand Down Expand Up @@ -1010,8 +1008,7 @@ impl RoundExecutor {
file_path = file_path
);

let content_messages =
Self::build_write_content_messages(ai_messages, &content_prompt);
let content_messages = Self::build_write_content_messages(ai_messages, &content_prompt);
let write_client = ai_client.with_reasoning_mode(ReasoningMode::Disabled);

let full_text = self
Expand Down Expand Up @@ -1912,8 +1909,8 @@ mod tests {

#[test]
fn write_content_messages_preserve_full_conversation_prefix() {
use bitfun_ai_adapters::types::{Message as AIMessage, ToolCall};
use crate::util::types::Message as CoreAIMessage;
use bitfun_ai_adapters::types::{Message as AIMessage, ToolCall};

let ai_messages = vec![
CoreAIMessage::user("Create the benchmark doc".to_string()),
Expand Down
18 changes: 10 additions & 8 deletions src/crates/core/src/agentic/persistence/session_branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,14 +346,16 @@ mod tests {
"parentSessionId": "legacy-parent",
"preservedKey": "preserved-value"
}));
source_metadata.relationship = Some(serde_json::from_value(serde_json::json!({
"kind": "deep_review",
"parentSessionId": "structured-parent",
"parentRequestId": "structured-request",
"parentDialogTurnId": "structured-turn",
"parentTurnIndex": 4
}))
.expect("relationship should deserialize"));
source_metadata.relationship = Some(
serde_json::from_value(serde_json::json!({
"kind": "deep_review",
"parentSessionId": "structured-parent",
"parentRequestId": "structured-request",
"parentDialogTurnId": "structured-turn",
"parentTurnIndex": 4
}))
.expect("relationship should deserialize"),
);
source_metadata.deep_review_run_manifest = Some(serde_json::json!({
"reviewMode": "deep",
"coreReviewers": [{ "subagentId": "ReviewBusinessLogic" }]
Expand Down
2 changes: 1 addition & 1 deletion src/crates/core/src/agentic/tools/file_tool_guidance.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Compatibility re-exports for file Write/Edit guardrail guidance markers.

pub use bitfun_agent_tools::{
FILE_TOOL_GUIDANCE_PREFIX, file_tool_guidance_message, is_file_tool_guidance_message,
file_tool_guidance_message, is_file_tool_guidance_message, FILE_TOOL_GUIDANCE_PREFIX,
};
Loading
Loading