From 5ce9df2e37904e48c357c63117ee1756a63ad035 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Thu, 14 May 2026 19:54:56 +0200 Subject: [PATCH] refactor: make bundled agents internal --- docs/dev/FILE-SYSTEM-MAP.md | 20 ++++++ docs/specs/sf-operating-model.md | 26 ++++++++ docs/user-docs/commands.md | 6 ++ scripts/check-sf-extension-inventory.mjs | 1 + .../agents/{review-code.md => critic.md} | 3 +- src/resources/agents/debugger.md | 1 + src/resources/agents/doc-writer.md | 1 + src/resources/agents/git-ops.md | 1 + src/resources/agents/javascript-pro.md | 1 + src/resources/agents/planner.md | 1 + src/resources/agents/refactorer.md | 1 + src/resources/agents/researcher.md | 1 + src/resources/agents/reviewer.md | 1 + src/resources/agents/scout.md | 1 + src/resources/agents/security.md | 1 + src/resources/agents/tester.md | 1 + src/resources/agents/typescript-pro.md | 1 + src/resources/agents/worker.md | 1 + .../sf/agents/review-code.agent.yaml | 1 + .../sf/agents/triage-decider.agent.yaml | 1 + src/resources/extensions/sf/commands-agent.js | 8 ++- .../extensions/sf/commands/catalog.js | 10 +-- .../extensions/sf/commands/handlers/ops.js | 62 ++----------------- .../extensions/sf/extension-manifest.json | 2 - .../extensions/sf/prompts/execute-task.md | 2 +- .../sf/skills/dispatching-subagents/SKILL.md | 9 ++- .../extensions/sf/subagent/agents.js | 6 ++ src/resources/extensions/sf/subagent/index.js | 30 ++++++--- .../sf/tests/subagent-agent-yaml.test.mjs | 50 ++++++++++++++- 29 files changed, 161 insertions(+), 89 deletions(-) rename src/resources/agents/{review-code.md => critic.md} (98%) diff --git a/docs/dev/FILE-SYSTEM-MAP.md b/docs/dev/FILE-SYSTEM-MAP.md index 75ffb437f..7e1a61bf3 100644 --- a/docs/dev/FILE-SYSTEM-MAP.md +++ b/docs/dev/FILE-SYSTEM-MAP.md @@ -655,14 +655,34 @@ ### src/resources/agents/ +Bundled autonomous worker-pool subagents. SF is primarily built for autonomous +operation, not as a general CLI coder with a persona menu, so these are routing +primitives for SF workflows rather than user-facing agent products. Markdown +frontmatter is the default format for one-body worker prompts. Use `.agent.yaml` +when the agent needs structured runtime metadata such as `promptParts`, +tool-policy contracts, or workflow-gate semantics. + | File | System Label(s) | Description | |------|-----------------|-------------| +| critic.md | Internal Subagent | Adversarial pre-implementation review agent used by execution flows | +| reviewer.md | Internal Subagent | Structured code/design review agent | | javascript-pro.md | Subagent | JavaScript specialist agent definition | | typescript-pro.md | Subagent | TypeScript specialist agent definition | | worker.md | Subagent | Generic worker agent definition | | researcher.md | Subagent | Research and exploration agent definition | | scout.md | Subagent | Scout/pathfinding agent definition | +### src/resources/extensions/sf/agents/ + +SF-owned workflow agents. YAML is the canonical format here because these +agents are loaded as part of SF control flows and may declare named +`promptParts`, tool allowlists, and machine-checked output contracts. + +| File | System Label(s) | Description | +|------|-----------------|-------------| +| review-code.agent.yaml | SF Workflow Agent | Triage apply review gate; emits `review-code: agree` only when the plan can mutate state | +| triage-decider.agent.yaml | SF Workflow Agent | Plan-only self-feedback triage decider | + ### src/resources/skills/ | Skill Directory | System Label(s) | Description | diff --git a/docs/specs/sf-operating-model.md b/docs/specs/sf-operating-model.md index 665aedeb0..d16356171 100644 --- a/docs/specs/sf-operating-model.md +++ b/docs/specs/sf-operating-model.md @@ -28,6 +28,25 @@ The names below are separate axes. Do not use one as a synonym for another. The flow is the product behavior: how SF captures intent, plans work, applies policy, executes tasks, records evidence, and reports status. Flow behavior must not fork by UI surface. If a TUI run and a non-interactive run receive the same state, run control, and permission profile, they should follow the same control model. +## Product Modes + +SF's user-facing product modes are about how much intent has been captured and +how much control the operator wants to retain. They are not agent personas. + +- **Assisted build** — SF works step by step with the operator. It asks + clarifying questions when needed, proposes bounded next actions, and pauses at + important gates before continuing. +- **Plan/discuss** — the operator explores what they want, constraints, taste, + risks, and scope. On exit, SF converts the discussion into structured + database-backed planning state: milestones, slices, tasks, requirements, + decisions, or follow-up questions. +- **Autonomous** — SF attempts the full loop: research missing context, plan the + work, execute bounded units, verify, record evidence, update state, and keep + going until completion, policy, budget, confidence, or a gate stops it. + +Bundled agents are implementation machinery inside these modes. They should +not be presented as the product model. + ## Surface A surface is where a person or program drives or observes the flow. @@ -149,11 +168,18 @@ Markdown under `docs/specs/` is a human export for review, navigation, and git h SF source placement follows the same axis model. New code should extend the owning axis instead of creating parallel trees. +SF is not trying to be a general CLI coder with a menu of personas. SF is a +purpose-to-software runtime: it captures intent, plans work, dispatches bounded +autonomous operations, records evidence, and exposes operator control surfaces. +Agents, skills, prompt parts, and workflow templates exist to support that +runtime. + ### Core Flow - `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/next` commands, prompts, templates, doctors, schedule, and DB-backed state. - `src/resources/extensions/` owns bundled extension packages loaded into the runtime. - `src/resources/agents/`, `src/resources/skills/`, and `src/resources/workflows/` own bundled runtime resources, not independent product flows. +- SF is primarily designed for autonomous operation. Bundled agents are an internal worker pool for orchestration by default, not a user-facing marketplace or a CLI-coder persona menu. `src/resources/agents/*.md` is the simple one-body worker-agent format; `src/resources/extensions/sf/agents/*.agent.yaml` is the structured format for SF-owned workflow agents that need `promptParts`, tool-policy contracts, or gate output contracts. ### Surfaces diff --git a/docs/user-docs/commands.md b/docs/user-docs/commands.md index 735e665ba..a829a8f6b 100644 --- a/docs/user-docs/commands.md +++ b/docs/user-docs/commands.md @@ -1,5 +1,11 @@ # Commands Reference +SF is organized around product modes, not a menu of coding-agent personas: +assisted build (`/next`) advances one bounded step at a time with operator +control, plan/discuss (`/discuss`) turns conversation into structured +milestones/slices/tasks on exit, and autonomous (`/autonomous`) runs the full +research-plan-execute-verify loop until a gate stops it. + ## Session Commands | Command | Description | diff --git a/scripts/check-sf-extension-inventory.mjs b/scripts/check-sf-extension-inventory.mjs index 0e70f3de8..0b7001114 100644 --- a/scripts/check-sf-extension-inventory.mjs +++ b/scripts/check-sf-extension-inventory.mjs @@ -39,6 +39,7 @@ const BASE_RUNTIME_COMMAND_NAMES = new Set([ ]); const HIDDEN_OR_ALIAS_SUBCOMMANDS = new Set([ "?", + "agent", // internal persistent-agent diagnostics, not part of the product command catalog "auto", "footer-config", // alias for /statusline "h", diff --git a/src/resources/agents/review-code.md b/src/resources/agents/critic.md similarity index 98% rename from src/resources/agents/review-code.md rename to src/resources/agents/critic.md index 0a4785ee0..09a0616c9 100644 --- a/src/resources/agents/review-code.md +++ b/src/resources/agents/critic.md @@ -1,8 +1,9 @@ --- -name: review-code +name: critic description: Constructive pre-implementation critic — catches design flaws, missing edge cases, and gaps before code is written model: sonnet tools: read, grep, find, ls, bash +visibility: internal --- You are a constructive critic. Your job is to identify real problems in a plan, design, or code change **before** implementation is committed to — when course corrections are still cheap. diff --git a/src/resources/agents/debugger.md b/src/resources/agents/debugger.md index 30b74c6ce..2af9c7cd1 100644 --- a/src/resources/agents/debugger.md +++ b/src/resources/agents/debugger.md @@ -2,6 +2,7 @@ name: debugger description: Hypothesis-driven bug investigation with root cause analysis model: sonnet +visibility: internal --- You are a debugger. Investigate bugs using a systematic, hypothesis-driven approach. Your goal is to find the root cause, not just suppress symptoms. diff --git a/src/resources/agents/doc-writer.md b/src/resources/agents/doc-writer.md index 72c550b56..e3364513b 100644 --- a/src/resources/agents/doc-writer.md +++ b/src/resources/agents/doc-writer.md @@ -2,6 +2,7 @@ name: doc-writer description: Documentation generation from code — API docs, inline comments, READMEs model: sonnet +visibility: internal --- You are a documentation specialist. You read code and produce clear, accurate documentation. You write for the reader, not the author — explain what they need to know to use or maintain the code. diff --git a/src/resources/agents/git-ops.md b/src/resources/agents/git-ops.md index 4bc73eef3..4dee7e111 100644 --- a/src/resources/agents/git-ops.md +++ b/src/resources/agents/git-ops.md @@ -2,6 +2,7 @@ name: git-ops description: Conflict resolution, rebase strategy, PR preparation, and changelog generation model: sonnet +visibility: internal --- You are a git operations specialist. You handle merge conflicts, plan rebase strategies, prepare pull requests, and generate changelogs. You understand git internals well enough to choose the right strategy for each situation. diff --git a/src/resources/agents/javascript-pro.md b/src/resources/agents/javascript-pro.md index 4b429c343..50f9e0f20 100644 --- a/src/resources/agents/javascript-pro.md +++ b/src/resources/agents/javascript-pro.md @@ -2,6 +2,7 @@ name: javascript-pro description: "Modern JavaScript specialist for browser, Node.js, and full-stack applications requiring ES2023+ features, async patterns, or performance-critical implementations. Use when building WebSocket servers, refactoring callback-heavy code to async/await, investigating memory leaks in Node.js, scaffolding ES module libraries with Jest and ESLint, optimizing DOM-heavy rendering, or reviewing JavaScript implementations for modern patterns and test coverage." model: sonnet +visibility: internal --- You are a senior JavaScript developer with mastery of modern JavaScript ES2023+ and Node.js 20+. You write production-grade code that prioritizes correctness, readability, performance, and maintainability — in that order. diff --git a/src/resources/agents/planner.md b/src/resources/agents/planner.md index cb630ffc6..e47316cc2 100644 --- a/src/resources/agents/planner.md +++ b/src/resources/agents/planner.md @@ -2,6 +2,7 @@ name: planner description: Architecture and implementation planning — outputs plans, not code model: sonnet +visibility: internal conflicts_with: plan-milestone, plan-slice, plan-task, research-milestone, research-slice --- diff --git a/src/resources/agents/refactorer.md b/src/resources/agents/refactorer.md index 92cd01134..9864abcfd 100644 --- a/src/resources/agents/refactorer.md +++ b/src/resources/agents/refactorer.md @@ -2,6 +2,7 @@ name: refactorer description: Safe code transformations — extract, inline, rename, simplify model: sonnet +visibility: internal --- You are a refactoring specialist. You perform safe, behavior-preserving code transformations. Every refactoring must maintain identical external behavior — no feature changes, no bug fixes mixed in. diff --git a/src/resources/agents/researcher.md b/src/resources/agents/researcher.md index ae8fba5da..c2eee56b3 100644 --- a/src/resources/agents/researcher.md +++ b/src/resources/agents/researcher.md @@ -2,6 +2,7 @@ name: researcher description: Web researcher that finds and synthesizes current information using Brave Search tools: search-the-web, bash +visibility: internal --- You are a web researcher. You find current, accurate information using web search and synthesize it into a clear, well-structured report. diff --git a/src/resources/agents/reviewer.md b/src/resources/agents/reviewer.md index c9599cb7d..780866e1f 100644 --- a/src/resources/agents/reviewer.md +++ b/src/resources/agents/reviewer.md @@ -2,6 +2,7 @@ name: reviewer description: Structured code review with severity ratings and actionable fixes model: sonnet +visibility: internal --- You are a code reviewer. Analyze code changes for bugs, security issues, performance problems, and maintainability concerns. Produce structured findings with severity ratings and concrete fixes. diff --git a/src/resources/agents/scout.md b/src/resources/agents/scout.md index b8c4ebcdd..41b51bb71 100644 --- a/src/resources/agents/scout.md +++ b/src/resources/agents/scout.md @@ -2,6 +2,7 @@ name: scout description: Fast codebase recon that returns compressed context for handoff to other agents tools: read, grep, find, ls, bash, codebase_search +visibility: internal --- You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything. diff --git a/src/resources/agents/security.md b/src/resources/agents/security.md index 3e1d2173a..8635aaaaf 100644 --- a/src/resources/agents/security.md +++ b/src/resources/agents/security.md @@ -2,6 +2,7 @@ name: security description: OWASP security audit, dependency risks, and secrets detection model: sonnet +visibility: internal --- You are a security auditor. Analyze code for vulnerabilities, insecure patterns, exposed secrets, and dependency risks. Focus on findings that are exploitable, not theoretical. diff --git a/src/resources/agents/tester.md b/src/resources/agents/tester.md index 6d8794e67..3be20774a 100644 --- a/src/resources/agents/tester.md +++ b/src/resources/agents/tester.md @@ -2,6 +2,7 @@ name: tester description: Test writing, fixing, and coverage gap identification model: sonnet +visibility: internal --- You are a testing specialist. Write tests, fix broken tests, and identify coverage gaps. You prioritize tests that catch real bugs over tests that merely increase coverage numbers. diff --git a/src/resources/agents/typescript-pro.md b/src/resources/agents/typescript-pro.md index a831b3829..e681fef63 100644 --- a/src/resources/agents/typescript-pro.md +++ b/src/resources/agents/typescript-pro.md @@ -2,6 +2,7 @@ name: typescript-pro description: "TypeScript specialist for advanced type system patterns, complex generics, type-level programming, and end-to-end type safety across full-stack applications. Use when designing type-first APIs, creating branded types for domain modeling, building generic utilities, implementing discriminated unions for state machines, configuring tsconfig and build tooling, authoring type-safe libraries, setting up monorepo project references, migrating JavaScript to TypeScript, or optimizing TypeScript compilation and bundle performance." model: sonnet +visibility: internal --- You are a senior TypeScript developer with mastery of TypeScript 5.0+ and its ecosystem. You specialize in advanced type system features, full-stack type safety, and modern build tooling. Types are the specification — start there. diff --git a/src/resources/agents/worker.md b/src/resources/agents/worker.md index 00bf5f5d1..b53bf01f2 100644 --- a/src/resources/agents/worker.md +++ b/src/resources/agents/worker.md @@ -1,6 +1,7 @@ --- name: worker description: General-purpose subagent with full capabilities, isolated context +visibility: internal --- You are a worker agent with full capabilities. You operate in an isolated context window to handle delegated tasks without polluting the main conversation. diff --git a/src/resources/extensions/sf/agents/review-code.agent.yaml b/src/resources/extensions/sf/agents/review-code.agent.yaml index 120156a90..1d9bb918c 100644 --- a/src/resources/extensions/sf/agents/review-code.agent.yaml +++ b/src/resources/extensions/sf/agents/review-code.agent.yaml @@ -1,5 +1,6 @@ name: review-code displayName: Review Code Agent +visibility: internal description: > A constructive critic and second opinion. Reviews proposals, designs, decision matrices, or implementations and surfaces ONLY weak points that diff --git a/src/resources/extensions/sf/agents/triage-decider.agent.yaml b/src/resources/extensions/sf/agents/triage-decider.agent.yaml index c4d3bb2a7..965ba0bcd 100644 --- a/src/resources/extensions/sf/agents/triage-decider.agent.yaml +++ b/src/resources/extensions/sf/agents/triage-decider.agent.yaml @@ -1,5 +1,6 @@ name: triage-decider displayName: Self-Feedback Triage Decider +visibility: internal description: > Reads the open self-feedback queue and proposes a decision plan (Fix, Promote, or Close per entry). PLAN-ONLY: this agent does NOT diff --git a/src/resources/extensions/sf/commands-agent.js b/src/resources/extensions/sf/commands-agent.js index a3b3640dd..311236ecb 100644 --- a/src/resources/extensions/sf/commands-agent.js +++ b/src/resources/extensions/sf/commands-agent.js @@ -1,9 +1,11 @@ /** * commands-agent.js — /agent command handler for persistent agent management. * - * Purpose: expose persistent agent state (identity, memory blocks, archival, inbox) - * as a first-class SF command surface so operators can inspect, reset, and delete - * named agents without touching the SQLite DB directly. + * Purpose: expose persistent agent state (identity, memory blocks, archival, + * inbox) as an internal operator/debug command so maintainers can inspect, + * reset, and delete named agents without touching the SQLite DB directly. + * This is not a normal user workflow; SF's product modes are assisted build, + * plan/discuss, and autonomous operation. * * Consumer: ops.js dispatcher for the /agent slash command. */ diff --git a/src/resources/extensions/sf/commands/catalog.js b/src/resources/extensions/sf/commands/catalog.js index e86471246..26f77b197 100644 --- a/src/resources/extensions/sf/commands/catalog.js +++ b/src/resources/extensions/sf/commands/catalog.js @@ -1,6 +1,6 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; -import { sfHome } from '../sf-home.js'; import { join } from "node:path"; +import { sfHome } from "../sf-home.js"; import { loadRegistry, workflowTemplateCommandDefinitions, @@ -157,10 +157,6 @@ export const TOP_LEVEL_SUBCOMMANDS = [ desc: "Switch to repair work mode and run diagnostics [--autonomous]", }, { cmd: "tasks", desc: "Background work surface — units, workers, budget" }, - { - cmd: "agent", - desc: "Persistent agent management — list|inspect|reset|delete named agents", - }, { cmd: "skills", desc: "List discovered skills from .agents/skills/ [reload|--eval|--auto-create]", @@ -299,10 +295,6 @@ export const TOP_LEVEL_SUBCOMMANDS = [ cmd: "keep-alive", desc: "Prevent system sleep during long runs (caffeinate / systemd-inhibit)", }, - { - cmd: "review-code", - desc: "Dispatch a review-code subagent for constructive pre-implementation review", - }, { cmd: "delegate", desc: "Create a GitHub PR from the current branch via gh pr create", diff --git a/src/resources/extensions/sf/commands/handlers/ops.js b/src/resources/extensions/sf/commands/handlers/ops.js index 76008a9c9..851c10b3f 100644 --- a/src/resources/extensions/sf/commands/handlers/ops.js +++ b/src/resources/extensions/sf/commands/handlers/ops.js @@ -437,7 +437,10 @@ Examples: ); return true; } - if (trimmed === "scaffold migrate" || trimmed.startsWith("scaffold migrate ")) { + if ( + trimmed === "scaffold migrate" || + trimmed.startsWith("scaffold migrate ") + ) { const { handleScaffoldMigrate } = await import( "../../commands-scaffold-migrate.js" ); @@ -510,14 +513,6 @@ Examples: await handleKeepAlive(trimmed.replace(/^keep-alive\s*/, "").trim(), ctx); return true; } - if ( - trimmed === "review-code" || - trimmed.startsWith("review-code ") - ) { - const input = trimmed.replace(/^review-code\s*/, "").trim(); - await handleReviewCodeCommand(input, ctx, pi); - return true; - } if (trimmed === "delegate" || trimmed.startsWith("delegate ")) { await handleDelegateCommand( trimmed.replace(/^delegate\s*/, "").trim(), @@ -608,55 +603,6 @@ async function handleKeepAlive(args, ctx) { } } -// ─── /review-code ──────────────────────────────────────────────────────────── - -async function handleReviewCodeCommand(topic, ctx, _pi) { - const { execSync } = await import("node:child_process"); - const root = projectRoot(); - - // Gather git diff for context (staged + unstaged, capped to avoid token bloat) - let diff = ""; - try { - const staged = execSync("git diff --cached --stat 2>/dev/null || true", { - cwd: root, - encoding: "utf-8", - }).trim(); - const unstaged = execSync("git diff --stat 2>/dev/null || true", { - cwd: root, - encoding: "utf-8", - }).trim(); - if (staged || unstaged) { - const fullDiff = execSync( - "git diff --cached 2>/dev/null; git diff 2>/dev/null", - { cwd: root, encoding: "utf-8" }, - ).slice(0, 8000); - diff = `\n\n## Current diff (truncated to 8 kB)\n\n\`\`\`diff\n${fullDiff}\n\`\`\``; - } - } catch { - // diff unavailable — not a hard failure - } - - const focus = topic ? `Focus on: ${topic}\n\n` : ""; - const reviewPrompt = - `Dispatch a \`review-code\` subagent to review the current plan or changes before proceeding. ` + - `Use the \`subagent\` tool with \`agent: "review-code"\`.\n\n` + - `${focus}` + - `Ask the review-code agent to identify blocking issues, non-blocking issues, and suggestions. ` + - `After the subagent returns, summarise the verdict and any blocking findings in one short paragraph. ` + - `Do not proceed with implementation until the user acknowledges blocking findings.` + - diff; - - ctx.ui.notify("Dispatching review-code review…", "info"); - try { - await ctx.sendMessage?.(reviewPrompt); - } catch { - ctx.ui.notify( - "Could not dispatch review-code. Try: subagent agent=review-code task='review current changes'", - "warning", - ); - } -} - // ─── /delegate ─────────────────────────────────────────────────────────────── async function handleDelegateCommand(args, ctx) { diff --git a/src/resources/extensions/sf/extension-manifest.json b/src/resources/extensions/sf/extension-manifest.json index ddf4092c0..d04bd5b7d 100644 --- a/src/resources/extensions/sf/extension-manifest.json +++ b/src/resources/extensions/sf/extension-manifest.json @@ -57,7 +57,6 @@ ], "commands": [ "add-tests", - "agent", "ask", "audit", "autonomous", @@ -145,7 +144,6 @@ "reset-slice", "rethink", "rewind", - "review-code", "run-hook", "scaffold", "scan", diff --git a/src/resources/extensions/sf/prompts/execute-task.md b/src/resources/extensions/sf/prompts/execute-task.md index 1e81213f9..3439dafcf 100644 --- a/src/resources/extensions/sf/prompts/execute-task.md +++ b/src/resources/extensions/sf/prompts/execute-task.md @@ -36,7 +36,7 @@ Then: 0. Narrate step transitions, key implementation decisions, and verification outcomes as you work. Keep it terse — one line between tool-call clusters, not between every call — but write complete sentences in user-facing prose, not shorthand notes or scratchpad fragments. 0a. **Batch independent tool calls in parallel.** When the next step needs to read or grep multiple files/paths that don't depend on each other's results, issue them in a single tool-call message (multiple tool uses in one assistant turn) rather than one-at-a-time. Examples: reading the handler + the test file + the schema file to triangulate a bug; greping for two unrelated symbols. Sequential tool calls are only correct when each call's input genuinely depends on the previous call's output. Talking-then-doing is also dead weight — if the next action is unambiguous, just take it; describe what you found in the result, not what you plan to look at. 0b. **Swarm opportunity check.** Before implementation, decide whether this task can be split into a 2-3 worker same-model swarm. Swarm only if the shards have disjoint file/directory ownership, no shared-interface or lockfile edits, shard-local verification, and clear wall-clock savings. If it passes, dispatch `subagent({ tasks: [...] })` with explicit write scopes, expected output files, and verification per worker; then inspect `git status --short`, synthesize results, resolve conflicts, and run final task verification yourself. If it does not pass, continue single-agent execution without ceremony. -0c. **Review-code check (non-trivial tasks only).** If the task touches more than two files, introduces a new abstraction, changes an API boundary, or has a non-obvious failure mode — dispatch a `review-code` subagent with the task plan and any relevant existing code as context. Summarise its verdict in one line. If it returns a **Blocking** finding, address it before writing code. Skip this step for simple edits, test fixes, or renaming tasks. +0c. **Critic check (non-trivial tasks only).** If the task touches more than two files, introduces a new abstraction, changes an API boundary, or has a non-obvious failure mode — dispatch a `critic` subagent with the task plan and any relevant existing code as context. Summarise its verdict in one line. If it returns a **Blocking** finding, address it before writing code. Skip this step for simple edits, test fixes, or renaming tasks. 1. {{skillActivation}} Follow any activated skills before writing code. If no skills match this task, skip this step. 2. **Verify file existence before editing.** The task plan references specific files. Before reading or editing any file mentioned in the plan, confirm it exists with `ls`, `find`, or `existsSync`. If a referenced file does NOT exist, stop immediately — do not attempt to create it based on the plan's description of what "should" be there. The file may have been deleted, renamed, or moved. Escalate as `blocker_discovered: true` with a clear description of which file is missing and what the plan expected to find. This prevents phantom work on stale file paths. 3. Execute the steps in the inlined task plan, adapting minor local mismatches when the surrounding code differs from the planner's snapshot diff --git a/src/resources/extensions/sf/skills/dispatching-subagents/SKILL.md b/src/resources/extensions/sf/skills/dispatching-subagents/SKILL.md index 14ec679c8..da0ec1d58 100644 --- a/src/resources/extensions/sf/skills/dispatching-subagents/SKILL.md +++ b/src/resources/extensions/sf/skills/dispatching-subagents/SKILL.md @@ -153,11 +153,14 @@ set. ### Agent selection and model overrides sf routes subagents through agent definitions in `src/resources/agents/`, -`~/.sf/agent/agents/`, or project `.sf/agents/`. The actual tool schema uses -`agent`, `task`, optional per-task `model`, optional `cwd`, plus batch-level -`mode`/`rounds` for debates. +`~/.sf/agent/agents/`, or project `.sf/agents/`. SF is not a general CLI coder +with a menu of personas; bundled agents are primarily SF's internal autonomous +worker pool. Direct human invocation is an operator/debug path. The actual tool +schema uses `agent`, `task`, optional per-task `model`, optional `cwd`, plus +batch-level `mode`/`rounds` for debates. - `planner` — architecture and implementation planning; conflicts with active sf planning phases. +- `critic` — adversarial pre-implementation review; surfaces blocking flaws before code is written. - `scout` — fast codebase recon. - `researcher` — web/current-info research. - `reviewer` — independent code/design review. diff --git a/src/resources/extensions/sf/subagent/agents.js b/src/resources/extensions/sf/subagent/agents.js index ba2bcf37e..b5496b4c5 100644 --- a/src/resources/extensions/sf/subagent/agents.js +++ b/src/resources/extensions/sf/subagent/agents.js @@ -60,6 +60,9 @@ function parseAgentTools(value) { function parseAgentModel(value) { return typeof value === "string" && value.trim() ? value.trim() : undefined; } +function parseAgentVisibility(value) { + return value === "internal" ? "internal" : "public"; +} function isAgentFileName(name) { return ( name.endsWith(".md") || @@ -75,6 +78,7 @@ function parseMarkdownAgent(content) { description: frontmatter.description, tools: frontmatter.tools, model: frontmatter.model, + visibility: frontmatter.visibility, conflictsWith: frontmatter.conflicts_with, systemPrompt: body, }; @@ -87,6 +91,7 @@ function parseYamlAgent(content) { description: doc.description, tools: doc.tools, model: doc.model, + visibility: doc.visibility, conflictsWith: doc.conflicts_with ?? doc.conflictsWith, systemPrompt: doc.prompt, promptParts: doc.promptParts, @@ -139,6 +144,7 @@ function loadAgentsFromDir(dir, source) { description: definition.description, tools: tools && tools.length > 0 ? tools : undefined, model: parseAgentModel(definition.model), + visibility: parseAgentVisibility(definition.visibility), conflictsWith, promptParts: definition.promptParts, sidekick: definition.sidekick, diff --git a/src/resources/extensions/sf/subagent/index.js b/src/resources/extensions/sf/subagent/index.js index 338441d1a..ae60b629b 100644 --- a/src/resources/extensions/sf/subagent/index.js +++ b/src/resources/extensions/sf/subagent/index.js @@ -1637,22 +1637,34 @@ export default function (pi) { }); // /subagent command - list available agents pi.registerCommand("subagent", { - description: "List available subagents", - handler: async (_args, ctx) => { + description: + "List public subagents; /subagent all shows SF's internal autonomous worker pool", + handler: async (args, ctx) => { const discovery = discoverAgents(ctx.cwd, "both"); - if (discovery.agents.length === 0) { + const showAll = ["all", "--all", "-a"].includes(args.trim()); + const visibleAgents = showAll + ? discovery.agents + : discovery.agents.filter((agent) => agent.visibility !== "internal"); + if (visibleAgents.length === 0) { ctx.ui.notify( - "No agents found. Add .md files to ~/.sf/agent/agents/ or .sf/agents/", - "warning", + discovery.agents.length === 0 + ? "No agents found. Add .md/.agent.yaml files to ~/.sf/agent/agents/ or .sf/agents/" + : "SF ships its bundled agents as an internal autonomous worker pool. Run /subagent all to inspect them.", + discovery.agents.length === 0 ? "warning" : "info", ); return; } - const lines = discovery.agents.map( + const hiddenCount = discovery.agents.length - visibleAgents.length; + const lines = visibleAgents.map( (a) => ` ${a.name} [${a.source}]${a.model ? ` (${a.model})` : ""}: ${a.description}`, ); + const suffix = + hiddenCount > 0 + ? `\n\n${hiddenCount} internal autonomous agent${hiddenCount === 1 ? "" : "s"} hidden. Run /subagent all to inspect them.` + : ""; ctx.ui.notify( - `Available agents (${discovery.agents.length}):\n${lines.join("\n")}`, + `Available agents (${visibleAgents.length}${showAll ? " total" : " public"}):\n${lines.join("\n")}${suffix}`, "info", ); }, @@ -1664,8 +1676,8 @@ export default function (pi) { "Delegate tasks to specialized subagents with isolated context windows.", "Each subagent is a separate pi process with its own tools, model, and system prompt.", "Modes: single ({ agent, task }), parallel ({ tasks: [{agent, task},...] }), debate ({ mode: 'debate', rounds, tasks: [...] }), chain ({ chain: [{agent, task},...] } with {previous} placeholder).", - "Agents are defined as .md files in ~/.sf/agent/agents/ (user) or .sf/agents/ (project).", - "Use the /subagent command to list available agents and their descriptions.", + "Agents are defined as .md or .agent.yaml files in ~/.sf/agent/agents/ (user) or .sf/agents/ (project).", + "SF's bundled agents are primarily an internal autonomous worker pool; use /subagent all to inspect them when debugging orchestration.", "Use chain mode to pipeline: scout finds context, planner designs, worker implements.", ].join(" "), promptGuidelines: [ diff --git a/src/resources/extensions/sf/tests/subagent-agent-yaml.test.mjs b/src/resources/extensions/sf/tests/subagent-agent-yaml.test.mjs index 0e4d5dd97..2e2975589 100644 --- a/src/resources/extensions/sf/tests/subagent-agent-yaml.test.mjs +++ b/src/resources/extensions/sf/tests/subagent-agent-yaml.test.mjs @@ -1,7 +1,13 @@ import assert from "node:assert/strict"; -import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { test } from "vitest"; import { discoverAgents, validateAgentDefinition } from "../subagent/agents.js"; @@ -118,7 +124,7 @@ test("discoverAgents_when_scope_both_includes_builtin_review_code_and_triage_dec // must be discoverable without operator setup. They're the foundation for // SF's self-driven triage pipeline (sf-mp5lnlbc-ty5fec). Isolate from the // real ~/.sf/agent/agents/ so the test doesn't conflict with the - // operator's personal review-code.md if present. + // operator's personal agents if present. const isolatedAgentDir = mkdtempSync(join(tmpdir(), "sf-agent-dir-")); const originalEnv = process.env.SF_CODING_AGENT_DIR; process.env.SF_CODING_AGENT_DIR = isolatedAgentDir; @@ -286,3 +292,41 @@ test("discoverAgents_when_scope_both_validates_builtin_promptParts_contract", () else process.env.SF_CODING_AGENT_DIR = originalEnv; } }); + +test("discoverAgents_when_loading_bundled_agents_marks_them_internal_by_default_policy", () => { + const isolatedAgentDir = mkdtempSync(join(tmpdir(), "sf-agent-dir-")); + const originalEnv = process.env.SF_CODING_AGENT_DIR; + process.env.SF_CODING_AGENT_DIR = isolatedAgentDir; + try { + const project = makeProject(); + const { agents } = discoverAgents(project, "both"); + const bundled = agents.filter((entry) => entry.source === "builtin"); + + assert.ok(bundled.length > 0, "expected bundled SF agents"); + for (const agent of bundled) { + assert.equal( + agent.visibility, + "internal", + `${agent.name} should be internal; SF uses bundled agents as an autonomous worker pool, not a user-facing CLI-coder palette`, + ); + } + } finally { + if (originalEnv === undefined) delete process.env.SF_CODING_AGENT_DIR; + else process.env.SF_CODING_AGENT_DIR = originalEnv; + } +}); + +test("bundled_markdown_agents_are_internal_autonomous_workers_not_public_palette", () => { + const agentsDir = resolve(import.meta.dirname, "..", "..", "..", "agents"); + const files = readdirSync(agentsDir).filter((file) => file.endsWith(".md")); + + assert.ok(files.length > 0, "expected bundled markdown agents"); + for (const file of files) { + const content = readFileSync(join(agentsDir, file), "utf8"); + assert.match( + content, + /^visibility:\s*internal$/m, + `${file} should be internal; SF is autonomous-first, not a CLI-coder agent marketplace`, + ); + } +});