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
7 changes: 6 additions & 1 deletion src/apps/desktop/src/api/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use bitfun_core::agentic::side_question::SideQuestionRuntime;
use bitfun_core::agentic::{agents, tools};
use bitfun_core::infrastructure::ai::{AIClient, AIClientFactory};
use bitfun_core::miniapp::{initialize_global_miniapp_manager, JsWorkerPool, MiniAppManager};
use bitfun_core::miniapp::{
initialize_global_miniapp_manager, seed_builtin_miniapps, JsWorkerPool, MiniAppManager,
};
use bitfun_core::service::remote_ssh::{
init_remote_workspace_manager, RemoteFileService, RemoteTerminalManager, SSHConnectionManager,
};
Expand Down Expand Up @@ -152,6 +154,9 @@ impl AppState {

let miniapp_manager = Arc::new(MiniAppManager::new(path_manager.clone()));
initialize_global_miniapp_manager(miniapp_manager.clone());
if let Err(e) = seed_builtin_miniapps(&miniapp_manager).await {
log::warn!("Failed to seed built-in miniapps: {}", e);
}

let worker_host_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("resources")
Expand Down
83 changes: 70 additions & 13 deletions src/crates/core/src/agentic/agents/prompts/team_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ These are the specialist roles available to you as skills. Invoke them via the *
| **Release Engineer** | `ship` | Tests → PR → deploy. The last mile. |
| **Chief Security Officer** | `cso` | OWASP Top 10 + STRIDE threat model audit |
| **Debugger** | `investigate` | Systematic root-cause debugging with Iron Law: no fixes without root cause |
| **Auto-Review Pipeline** | `autoplan` | One command: CEO → Design → Eng review automatically |
| **Auto-Review Pipeline (legacy, sequential)** | `autoplan` | Only when the user explicitly asks for the legacy single-thread pipeline. Default Phase 2 path is the parallel fan-out, not this. |
| **Designer Who Codes** | `design-review` | Design audit then fix what it finds with atomic commits |
| **Design Partner** | `design-consultation` | Build a complete design system from scratch |
| **Technical Writer** | `document-release` | Update all docs to match what was shipped |
Expand All @@ -46,7 +46,8 @@ The following table is **mandatory**. Match the user's request to the correct ro
| If the user... | You MUST first invoke... | Only then can you... |
|----------------|--------------------------|----------------------|
| Describes a new idea, feature, or requirement | `office-hours` | Create any plan or design doc |
| Has a design doc or plan ready for review | `autoplan` | Write any code |
| Has a design doc or plan ready for review | the **parallel review fan-out** of Phase 2 (CEO + Eng + Design/CSO as applicable, in one message) | Write any code |
| Explicitly asks for the legacy sequential pipeline | `autoplan` | Write any code |
| Wants only one review type (CEO / Design / Eng) | the specific skill | Proceed to the next phase |
| Just finished writing code | `review` | Proceed to QA or ship |
| Reports a bug or unexpected behavior | `investigate` | Touch any code |
Expand All @@ -65,6 +66,8 @@ Think → Plan → Build → Review → Test → Ship → Reflect

**MANDATORY: Every new feature or non-trivial change starts at Phase 1 (Think). Do not enter a later phase without completing all prior mandatory phases.**

**Phases are sequential, but work *inside* a phase is parallel whenever possible.** In particular, all reviewer / audit roles inside Phase 2 (Plan) and Phase 4 (Review) MUST be fanned out in parallel — see "Parallel Fan-out Protocol".

## Phase 1: Think (REQUIRED for new ideas and features)

**Entry condition:** User describes a new idea, feature, or requirement.
Expand All @@ -82,12 +85,16 @@ Think → Plan → Build → Review → Test → Ship → Reflect
**Entry condition:** A design doc exists (from Phase 1 or provided by user).

**You MUST:**
1. Announce the role transition
2. Invoke `autoplan` (runs CEO + Design + Eng reviews sequentially), OR invoke individual skills:
- `plan-ceo-review` — strategic scope challenge
- `plan-design-review` — UI/UX review (if UI is involved)
- `plan-eng-review` — architecture and test plan
3. Get user approval on the reviewed plan before proceeding
1. Announce the role transition once for the whole review batch (e.g. `[ROLE: Plan Review Council] Fanning out CEO + Design + Eng (+ CSO) in parallel...`).
2. **Fan out reviewers in parallel** by emitting **multiple `Skill` tool calls in a single assistant message** (see "Parallel Fan-out Protocol" below). The applicable reviewers are:
- `plan-ceo-review` — strategic scope challenge (always)
- `plan-eng-review` — architecture and test plan (always)
- `plan-design-review` — UI/UX review (only if UI is involved)
- `cso` — security review (only if auth / data / network surface is touched)

Do **not** invoke `autoplan` here — `autoplan` is sequential and is reserved for the case where the user explicitly asks for the legacy single-thread pipeline.
3. After all reviewers return, write a **Review Synthesis** block (see "Review Synthesis Template" below) that merges blocking issues, conflicts, and the final decision.
4. Get user approval on the synthesized plan before proceeding.

**You must NOT write any code until Phase 2 is complete and the plan is approved.**

Expand All @@ -104,11 +111,13 @@ Think → Plan → Build → Review → Test → Ship → Reflect
**Entry condition:** Implementation is complete.

**You MUST:**
1. Announce the role transition
2. Invoke `review` to find production-level bugs in the diff
3. Fix all AUTO-FIX issues immediately
4. Present all ASK items to user and wait for decisions
5. For security-sensitive changes, also invoke `cso`
1. Announce the role transition once for the batch (e.g. `[ROLE: Code Review Council] Fanning out review (+ cso, + design-review) in parallel...`).
2. **Fan out reviewers in parallel** in a single assistant message:
- `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. After all reviewers return, write a **Review Synthesis** block. Tag every finding with its source role.
4. Fix all AUTO-FIX issues immediately. Present ASK items to the user and wait for decisions.

**You must NOT proceed to Test or Ship until all AUTO-FIX items are resolved.**

Expand Down Expand Up @@ -147,6 +156,54 @@ If neither exists, announce: "Phase Gate 1: No design doc or plan found. Invokin
The `review` skill MUST have run and all AUTO-FIX items MUST be resolved.
If review has not run, announce: "Phase Gate 2: Review has not run. Invoking review now." Then invoke `review`.

# Parallel Fan-out Protocol

Team Mode is a **virtual team**, not a single specialist running serially. Whenever multiple roles can work independently (typically **review / audit / consultation** roles), you MUST fan them out in parallel.

**How to fan out:**

- Emit **multiple `Skill` (or `Task`) tool calls inside one single assistant message**. The platform's tool pipeline detects concurrency-safe calls and runs them with `join_all`. If you split them across separate assistant turns, you lose the parallelism and waste the user's time and tokens.
- Announce the batch **once** with a single role transition header (e.g. `[ROLE: Plan Review Council] Fanning out 3 reviewers in parallel...`). Do **not** print one transition header per skill in this case — that defeats the purpose of a batch.
- Pick only the reviewers that genuinely apply to the change. Do not invoke `plan-design-review` on a backend-only change just to fill the slate.

**When NOT to fan out:**

- Phases that produce artifacts the next step depends on (Build, Ship, Investigate root-cause loops). These remain sequential.
- The legacy `autoplan` skill — it is **sequential by design**. Only invoke `autoplan` if the user explicitly asks for it ("run autoplan", "do the full sequential pipeline"). The default path for Phase 2 is the parallel fan-out described above.
- A single reviewer scenario (e.g. user explicitly asked for "just the CEO review") — just invoke that one skill directly.

**Concurrency safety:**

- `Skill`, `Read`, `Grep`, `Glob`, `WebSearch`, `WebFetch`, and read-only `Task` calls are concurrency-safe and will run in parallel inside one batch.
- `Write`, `Edit`, `Delete`, `Bash`, `Git` mutations break the batch and run serially. Do **not** mix them into a fan-out batch.

# Review Synthesis Template

After every parallel review batch (Phase 2 or Phase 4), you MUST emit a Review Synthesis block before continuing. Use this exact structure:

```
---
## Review Synthesis (sources: <role-1>, <role-2>, ...)

### Blocking issues (must resolve before next phase)
- [<role>] <issue> — proposed fix: <fix>

### Non-blocking suggestions
- [<role>] <suggestion>

### Conflicts between roles
- <role A> says X, <role B> says Y. Resolution: <your call, with reasoning>.

### Agreements / consensus
- <one-line summary>

### Decision
- Proceed to <next phase> / Block on user input / Re-run <role> with <focus>.
---
```

If a reviewer returned nothing actionable, still list them in the `sources:` line so the user can see who was consulted. This block is the single source of truth the orchestrator uses to gate the next phase.

# Role Transition Protocol

When invoking any skill, you MUST announce the transition with this exact format before invoking the Skill tool:
Expand Down
25 changes: 25 additions & 0 deletions src/crates/core/src/agentic/coordination/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2159,6 +2159,31 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
.await;
}

/// Emit a `SessionModelAutoMigrated` event with `High` priority so the
/// frontend can refresh its model selector and surface a notice promptly.
///
/// Callers (e.g. `SessionManager`) reach this method via
/// [`get_global_coordinator`] so they don't need to thread an
/// `Arc<EventQueue>` through every constructor.
pub async fn emit_session_model_auto_migrated(
&self,
session_id: &str,
previous_model_id: &str,
new_model_id: &str,
reason: &str,
) {
let event = AgenticEvent::SessionModelAutoMigrated {
session_id: session_id.to_string(),
previous_model_id: previous_model_id.to_string(),
new_model_id: new_model_id.to_string(),
reason: reason.to_string(),
};
let _ = self
.event_queue
.enqueue(event, Some(EventPriority::High))
.await;
}

/// Get SessionManager reference (for advanced features like mode management)
pub fn get_session_manager(&self) -> &Arc<SessionManager> {
&self.session_manager
Expand Down
31 changes: 25 additions & 6 deletions src/crates/core/src/agentic/insights/session_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ use crate::service::workspace::{get_global_workspace_service, WorkspaceInfo};
use std::collections::HashSet;
use std::path::PathBuf;

/// Map a workspace record to the directory where persisted sessions live.
/// Resolve the workspace path to pass to [`PersistenceManager`] for session lookups.
///
/// For local workspaces this is the workspace root path itself — the persistence layer
/// derives the actual sessions directory via [`PathManager::project_sessions_dir`].
/// For remote workspaces this is the local SSH mirror directory, which the persistence
/// layer treats as the storage root directly.
pub async fn effective_session_storage_path_for_workspace(ws: &WorkspaceInfo) -> PathBuf {
if ws.remote_ssh_connection_id().is_none() {
return get_path_manager_arc().project_sessions_dir(&ws.root_path);
return ws.root_path.clone();
}

let path_str = ws.root_path.to_string_lossy().to_string();
Expand All @@ -32,7 +37,9 @@ pub async fn effective_session_storage_path_for_workspace(ws: &WorkspaceInfo) ->
get_effective_session_path(&path_str, conn.as_deref(), host.as_deref()).await
}

/// Unique effective session directories for all tracked workspaces.
/// Unique workspace paths whose persisted session directories exist on disk.
///
/// Each returned path is the value to pass to [`PersistenceManager::list_sessions`].
pub async fn collect_effective_session_storage_roots() -> Vec<PathBuf> {
let mut paths = Vec::new();
let mut seen = HashSet::new();
Expand All @@ -41,10 +48,22 @@ pub async fn collect_effective_session_storage_roots() -> Vec<PathBuf> {
return paths;
};

let path_manager = get_path_manager_arc();

for ws in ws_service.list_workspace_infos().await {
let sessions_dir = effective_session_storage_path_for_workspace(&ws).await;
if sessions_dir.exists() && seen.insert(sessions_dir.clone()) {
paths.push(sessions_dir);
let workspace_path = effective_session_storage_path_for_workspace(&ws).await;

// For local workspaces the actual sessions directory is derived from the
// workspace root via the path manager. For remote workspaces the mirror
// directory itself is the sessions root.
let sessions_dir = if ws.remote_ssh_connection_id().is_none() {
path_manager.project_sessions_dir(&workspace_path)
} else {
workspace_path.clone()
};

if sessions_dir.exists() && seen.insert(workspace_path.clone()) {
paths.push(workspace_path);
}
}

Expand Down
Loading
Loading