From a5563b3f3ab7258781dfa49aae260d4a82f1acd2 Mon Sep 17 00:00:00 2001 From: Khalid Alnujaidi Date: Fri, 8 May 2026 08:50:47 +0300 Subject: [PATCH] feat(skills): bundle delegate skill alongside skill-creator --- crates/tui/assets/skills/delegate/SKILL.md | 165 +++++++++++++++++ crates/tui/src/skills/system.rs | 206 ++++++++++++++------- 2 files changed, 307 insertions(+), 64 deletions(-) create mode 100755 crates/tui/assets/skills/delegate/SKILL.md diff --git a/crates/tui/assets/skills/delegate/SKILL.md b/crates/tui/assets/skills/delegate/SKILL.md new file mode 100755 index 00000000..7495e168 --- /dev/null +++ b/crates/tui/assets/skills/delegate/SKILL.md @@ -0,0 +1,165 @@ +--- +name: delegate +description: Strategic task delegation. Plan and reason first, then offload laborious execution work (scaffolding, code generation, boilerplate, search, tests, file creation) to deepseek-v4-flash sub-agents. Use when the user asks for multi-step implementation tasks, code generation, refactoring, or any request that involves both planning and execution phases. +--- + +# Delegate + +Plan the work, then ship the labor to `deepseek-v4-flash` sub-agents. + +## Core Principle + +The coordinating agent (you) handles reasoning, architecture, design decisions, and integration. Sub-agents running `deepseek-v4-flash` handle execution: scaffold generation, boilerplate, code writing, search/grep, test creation, and file edits. + +This gives the user Pro-quality reasoning with Flash-level cost on execution — each flash sub-agent costs ~10x less per token than Pro. + +## What to Delegate vs Keep + +| Keep in parent (Pro / current model) | Delegate to flash sub-agents | +|---|---| +| Understanding the user's request | Generating scaffold files | +| Architecture and design decisions | Writing boilerplate code | +| Trade-off analysis | Creating new files from scratch | +| Security review | Search and grep across the codebase | +| Integration across modules | Running tests and collecting results | +| Synthesis of sub-agent results | Reading and summarizing multiple files | +| Final quality check | Bulk edits following a clear spec | +| Ambiguous decisions | Deterministic code generation | + +Rule of thumb: if the task is well-specified, repetitive, or purely mechanical — delegate. If it requires judgment, trade-offs, or cross-cutting understanding — keep it. + +## Workflow + +### 1. Understand and Plan + +Read the user's request. Reason about what's needed. Identify: + +- What requires architectural thinking (keep) +- What is mechanical execution (delegate) +- Dependencies between sub-tasks + +Use `checklist_write` to lay out the plan. Mark items as delegate vs direct. + +### 2. Identify Delegation Units + +Split execution work into independent, self-contained units. Each unit should be: + +- **Self-contained**: has all context needed to complete (include file paths, specs, conventions) +- **Independent**: doesn't depend on another sub-agent's output +- **Verifiable**: has a clear acceptance criterion + +Good delegation prompts are specific and bounded: + +``` +"Create src/auth/login.rs with a LoginForm struct containing +email and password fields. Derive Serialize, Deserialize, Debug. +Add validation that email is non-empty and password is >= 8 chars." +``` + +Bad (vague, needs judgment): + +``` +"Write the auth module." +``` + +### 3. Spawn Flash Sub-Agents + +Use `agent_spawn` with `model: "deepseek-v4-flash"` for each independent unit. +Spawn them together in one turn for parallel execution: + +```json +// agent_spawn call 1 +{ + "prompt": "Create src/models/user.rs: User struct with id, name, email...", + "model": "deepseek-v4-flash", + "type": "implementer" +} + +// agent_spawn call 2 (same turn — runs in parallel) +{ + "prompt": "Create src/models/post.rs: Post struct with id, title, body...", + "model": "deepseek-v4-flash", + "type": "implementer" +} +``` + +Key parameters: + +- **`model`**: always `"deepseek-v4-flash"` for execution work +- **`type`**: `"implementer"` for code generation, `"explore"` for read-only search/investigation, `"general"` for mixed work +- **`cwd`**: set to the workspace root when the child needs file access +- **`allowed_tools`**: narrow to only what's needed (e.g., `["read_file", "write_file", "grep_files"]` for code gen) + +### 4. Wait and Synthesize + +Use `agent_wait` to collect results from parallel sub-agents. Then: + +1. Verify each sub-agent's output (don't trust blindly) +2. Cross-check one finding against a direct `read_file` +3. Integrate results into a coherent whole +4. Run verification gates: `cargo check`, `cargo test`, etc. + +### 5. Iterate if Needed + +If a sub-agent's output needs adjustment, either: +- Fix it directly (small tweaks) +- Spawn a follow-up flash sub-agent with the correction (non-trivial rework) + +## Parallel Delegation Pattern + +Batch independent spawns in a single turn. The dispatcher runs them concurrently: + +``` +Turn N: + - agent_spawn: create user.rs (flash) + - agent_spawn: create post.rs (flash) + - agent_spawn: create error.rs (flash) + → all three run in parallel + +Turn N+1: + - agent_wait: all three + - cargo check to verify + - read_file to spot-check + - synthesize results for the user +``` + +Don't serialize when you can parallelize. Three flash sub-agents in one turn finish faster and cost the same as three sequential turns. + +## What NOT to Delegate + +- The initial understanding and planning phase (you need full context) +- Architecture decisions that span modules +- Security-sensitive code paths +- Final integration and verification +- Tasks where the spec is ambiguous and needs judgment +- Very small tasks (sub-agent overhead isn't worth it for a single-line edit) + +## Cost Awareness + +Flash is ~10x cheaper than Pro per token. A typical flash sub-agent doing file creation costs pennies. The parent turn (planning + synthesis) is where the expensive reasoning lives. + +Batch parallel sub-agents when possible — the cost is the same as sequential but the user waits less. + +## Quick Reference + +``` +# Spawn a flash sub-agent for code generation +agent_spawn { + prompt: "detailed, self-contained task description", + model: "deepseek-v4-flash", + type: "implementer" +} + +# Spawn a flash sub-agent for search/investigation +agent_spawn { + prompt: "search task description", + model: "deepseek-v4-flash", + type: "explore" +} + +# Wait for all running sub-agents +agent_wait { wait_mode: "all" } + +# Get a specific result +agent_result { agent_id: "...", block: true } +``` diff --git a/crates/tui/src/skills/system.rs b/crates/tui/src/skills/system.rs index 96823888..e5a10d07 100644 --- a/crates/tui/src/skills/system.rs +++ b/crates/tui/src/skills/system.rs @@ -1,65 +1,99 @@ -//! System-skill installer: bundles skill-creator and auto-installs it on first launch. +//! System-skill installer: bundles skill-creator and delegate, auto-installs +//! them on first launch. use std::fs; use std::path::Path; -const BUNDLED_SKILL_VERSION: &str = "1"; +const BUNDLED_SKILL_VERSION: &str = "2"; const SKILL_CREATOR_BODY: &str = include_str!("../../assets/skills/skill-creator/SKILL.md"); +const DELEGATE_BODY: &str = include_str!("../../assets/skills/delegate/SKILL.md"); -/// Install bundled system skills into `skills_dir`. +/// Attempt to install a single bundled skill into `skills_dir`. /// -/// Behaviour: -/// - Fresh install (no marker, no dir): installs `skill-creator/SKILL.md` and writes -/// the version marker. -/// - Version bump (marker present with older version, dir present): re-installs. -/// - User deleted the dir while marker still present at same version: leaves it gone. -/// - Idempotent: calling twice with no changes is a no-op. -/// -/// Errors are I/O errors from the filesystem; the caller should log them but not -/// abort startup. -pub fn install_system_skills(skills_dir: &Path) -> std::io::Result<()> { - let marker = skills_dir.join(".system-installed-version"); - let target_dir = skills_dir.join("skill-creator"); +/// Returns `true` if installation occurred (fresh install or version bump). +fn install_one( + skills_dir: &Path, + name: &str, + body: &str, + installed_version: Option<&str>, +) -> std::io::Result { + let target_dir = skills_dir.join(name); let target_file = target_dir.join("SKILL.md"); - - let installed_version = fs::read_to_string(&marker) - .ok() - .map(|s| s.trim().to_string()); let dir_exists = target_dir.exists(); // Re-install only when BOTH conditions hold: // (a) bundled version is newer than what is recorded in the marker, AND // (b) the skill directory still exists (user hasn't intentionally deleted it). // Fresh install (no marker AND no dir) is also handled. - let should_install = match (installed_version.as_deref(), dir_exists) { + let should_install = match (installed_version, dir_exists) { // Fresh install: neither marker nor directory. (None, false) => true, // Version bump: marker is outdated but directory still present. - (Some(v), true) if v != BUNDLED_SKILL_VERSION => true, + (Some(v), _) if v != BUNDLED_SKILL_VERSION => true, // Every other case: already installed at current version, or user deleted // the dir (respect that choice). _ => false, }; if should_install { - fs::create_dir_all(skills_dir)?; fs::create_dir_all(&target_dir)?; - fs::write(&target_file, SKILL_CREATOR_BODY)?; + fs::write(&target_file, body)?; + } + Ok(should_install) +} + +/// Install bundled system skills into `skills_dir`. +/// +/// Behaviour: +/// - Fresh install (no marker, no dir): installs `skill-creator/SKILL.md` and +/// `delegate/SKILL.md`, then writes the version marker. +/// - Version bump (marker present with older version): re-installs any skill +/// whose directory still exists. +/// - User deleted a skill dir while marker still present at same version: leaves +/// it gone. +/// - Idempotent: calling twice with no changes is a no-op. +/// +/// Errors are I/O errors from the filesystem; the caller should log them but not +/// abort startup. +pub fn install_system_skills(skills_dir: &Path) -> std::io::Result<()> { + let marker = skills_dir.join(".system-installed-version"); + + let installed_version = fs::read_to_string(&marker) + .ok() + .map(|s| s.trim().to_string()); + + let sc_installed = install_one( + skills_dir, + "skill-creator", + SKILL_CREATOR_BODY, + installed_version.as_deref(), + )?; + let dg_installed = install_one( + skills_dir, + "delegate", + DELEGATE_BODY, + installed_version.as_deref(), + )?; + + if sc_installed || dg_installed { + fs::create_dir_all(skills_dir)?; fs::write(&marker, BUNDLED_SKILL_VERSION)?; } Ok(()) } -/// Remove the `skill-creator` system skill and its version marker. +/// Remove all system skills and the version marker. /// /// Intended for tests and `deepseek setup --clean`. Ignores missing files. #[allow(dead_code)] pub fn uninstall_system_skills(skills_dir: &Path) -> std::io::Result<()> { let marker = skills_dir.join(".system-installed-version"); - let target_dir = skills_dir.join("skill-creator"); - if target_dir.exists() { - fs::remove_dir_all(&target_dir)?; + for name in &["skill-creator", "delegate"] { + let dir = skills_dir.join(name); + if dir.exists() { + fs::remove_dir_all(&dir)?; + } } if marker.exists() { fs::remove_file(&marker)?; @@ -74,10 +108,22 @@ mod tests { // ── helpers ────────────────────────────────────────────────────────────── - fn skill_file(tmp: &TempDir) -> std::path::PathBuf { + fn sc_file(tmp: &TempDir) -> std::path::PathBuf { tmp.path().join("skill-creator").join("SKILL.md") } + fn dg_file(tmp: &TempDir) -> std::path::PathBuf { + tmp.path().join("delegate").join("SKILL.md") + } + + fn sc_dir(tmp: &TempDir) -> std::path::PathBuf { + tmp.path().join("skill-creator") + } + + fn dg_dir(tmp: &TempDir) -> std::path::PathBuf { + tmp.path().join("delegate") + } + fn marker_file(tmp: &TempDir) -> std::path::PathBuf { tmp.path().join(".system-installed-version") } @@ -85,11 +131,12 @@ mod tests { // ── fresh install ───────────────────────────────────────────────────────── #[test] - fn fresh_install_creates_skill_and_marker() { + fn fresh_install_creates_both_skills_and_marker() { let tmp = TempDir::new().unwrap(); install_system_skills(tmp.path()).unwrap(); - assert!(skill_file(&tmp).exists(), "SKILL.md should be created"); + assert!(sc_file(&tmp).exists(), "skill-creator SKILL.md should be created"); + assert!(dg_file(&tmp).exists(), "delegate SKILL.md should be created"); assert!(marker_file(&tmp).exists(), "marker should be created"); let ver = fs::read_to_string(marker_file(&tmp)).unwrap(); @@ -103,78 +150,109 @@ mod tests { let tmp = TempDir::new().unwrap(); install_system_skills(tmp.path()).unwrap(); - // Overwrite SKILL.md with sentinel to detect an undesired second write. - fs::write(skill_file(&tmp), "sentinel").unwrap(); + // Overwrite both SKILL.md files with sentinels to detect undesired writes. + fs::write(sc_file(&tmp), "sc-sentinel").unwrap(); + fs::write(dg_file(&tmp), "dg-sentinel").unwrap(); install_system_skills(tmp.path()).unwrap(); - let contents = fs::read_to_string(skill_file(&tmp)).unwrap(); - assert_eq!( - contents, "sentinel", - "second install should not overwrite SKILL.md when version is current" - ); + let sc = fs::read_to_string(sc_file(&tmp)).unwrap(); + let dg = fs::read_to_string(dg_file(&tmp)).unwrap(); + assert_eq!(sc, "sc-sentinel", "second install should not overwrite skill-creator"); + assert_eq!(dg, "dg-sentinel", "second install should not overwrite delegate"); } - // ── user deleted the directory ──────────────────────────────────────────── + // ── user deleted a directory ────────────────────────────────────────────── #[test] fn user_deleted_dir_is_not_recreated() { let tmp = TempDir::new().unwrap(); install_system_skills(tmp.path()).unwrap(); - // Simulate user deliberately removing the skill directory. - fs::remove_dir_all(tmp.path().join("skill-creator")).unwrap(); + // Simulate user deliberately removing one skill directory. + fs::remove_dir_all(dg_dir(&tmp)).unwrap(); - // Re-launch must NOT recreate the directory. + // Re-launch must NOT recreate the deleted directory. install_system_skills(tmp.path()).unwrap(); assert!( - !skill_file(&tmp).exists(), - "skill-creator must not be recreated after user deleted it" + !dg_file(&tmp).exists(), + "delegate must not be recreated after user deleted it" ); + assert!( + sc_file(&tmp).exists(), + "skill-creator should still be present (not deleted by user)" + ); + } + + #[test] + fn user_deleted_both_dirs_are_not_recreated() { + let tmp = TempDir::new().unwrap(); + install_system_skills(tmp.path()).unwrap(); + + fs::remove_dir_all(sc_dir(&tmp)).unwrap(); + fs::remove_dir_all(dg_dir(&tmp)).unwrap(); + + install_system_skills(tmp.path()).unwrap(); + + assert!(!sc_file(&tmp).exists()); + assert!(!dg_file(&tmp).exists()); } // ── version bump re-installs ────────────────────────────────────────────── #[test] - fn outdated_marker_triggers_reinstall() { + fn outdated_marker_triggers_reinstall_of_existing_skills() { let tmp = TempDir::new().unwrap(); - // Simulate a previous install at a lower version. - let skill_dir = tmp.path().join("skill-creator"); - fs::create_dir_all(&skill_dir).unwrap(); - fs::write(skill_dir.join("SKILL.md"), "old content").unwrap(); + // Simulate a previous install at a lower version with both skills present. + fs::create_dir_all(sc_dir(&tmp)).unwrap(); + fs::write(sc_file(&tmp), "old-sc").unwrap(); + fs::create_dir_all(dg_dir(&tmp)).unwrap(); + fs::write(dg_file(&tmp), "old-dg").unwrap(); fs::write(marker_file(&tmp), "0").unwrap(); // older than BUNDLED_SKILL_VERSION install_system_skills(tmp.path()).unwrap(); - let contents = fs::read_to_string(skill_file(&tmp)).unwrap(); - assert_ne!( - contents, "old content", - "outdated skill should be overwritten on version bump" - ); - assert_eq!( - contents, SKILL_CREATOR_BODY, - "re-installed file must match the bundled body" - ); + let sc = fs::read_to_string(sc_file(&tmp)).unwrap(); + let dg = fs::read_to_string(dg_file(&tmp)).unwrap(); + assert_ne!(sc, "old-sc", "outdated skill-creator should be overwritten"); + assert_ne!(dg, "old-dg", "outdated delegate should be overwritten"); + assert_eq!(sc, SKILL_CREATOR_BODY); + assert_eq!(dg, DELEGATE_BODY); let ver = fs::read_to_string(marker_file(&tmp)).unwrap(); - assert_eq!( - ver.trim(), - BUNDLED_SKILL_VERSION, - "marker should be updated" - ); + assert_eq!(ver.trim(), BUNDLED_SKILL_VERSION); + } + + // ── partial previous install (only skill-creator existed) ───────────────── + + #[test] + fn version_bump_adds_delegate_when_it_was_missing() { + let tmp = TempDir::new().unwrap(); + + // Simulate state from v1: only skill-creator present. + fs::create_dir_all(sc_dir(&tmp)).unwrap(); + fs::write(sc_file(&tmp), "old-sc").unwrap(); + fs::write(marker_file(&tmp), "1").unwrap(); + + install_system_skills(tmp.path()).unwrap(); + + // skill-creator should be updated, delegate should be newly installed. + assert_eq!(fs::read_to_string(sc_file(&tmp)).unwrap(), SKILL_CREATOR_BODY); + assert_eq!(fs::read_to_string(dg_file(&tmp)).unwrap(), DELEGATE_BODY); } // ── uninstall ───────────────────────────────────────────────────────────── #[test] - fn uninstall_removes_skill_and_marker() { + fn uninstall_removes_both_skills_and_marker() { let tmp = TempDir::new().unwrap(); install_system_skills(tmp.path()).unwrap(); uninstall_system_skills(tmp.path()).unwrap(); - assert!(!skill_file(&tmp).exists(), "SKILL.md should be removed"); + assert!(!sc_file(&tmp).exists(), "skill-creator should be removed"); + assert!(!dg_file(&tmp).exists(), "delegate should be removed"); assert!(!marker_file(&tmp).exists(), "marker should be removed"); }