Skip to content
Open
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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Gentle-AI is NOT an AI agent installer. Most agents are easy to install. It is a
| **Kilo Code** | Full (multi-mode overlay) | OpenCode-compatible config in `~/.config/kilo` |
| **Gemini CLI** | Full (experimental) | Custom agents in `~/.gemini/agents/` |
| **Cursor** | Full (native subagents) | 10 SDD agents in `~/.cursor/agents/` |
| **VS Code Copilot** | Full (runSubagent) | Parallel execution |
| **VS Code Copilot** | Full (native subagents) | `~/.copilot/agents` SDD agents with model fallback |
| **Codex** | Solo-agent | CLI-native, TOML config |
| **Windsurf** | Solo-agent | Plan Mode, Code Mode, native workflows |
| **Antigravity** | Solo-agent + Mission Control | Built-in Browser/Terminal sub-agents |
Expand Down Expand Up @@ -190,6 +190,14 @@ After creating a profile, open OpenCode and press **Tab** to switch between `gen

**Full guide**: [OpenCode SDD Profiles](docs/opencode-profiles.md)

### VS Code Copilot SDD Agents

Gentle-AI installs three VS Code layers: global instructions/rules in `Code/User/prompts/gentle-ai.instructions.md`, native custom agents in `~/.copilot/agents`, and SDD skills/shared conventions in `~/.copilot/skills`.

You can assign GitHub Copilot models per coordinator or SDD phase from the TUI. If a model cache is missing, invalid, or unvalidated, generated agents omit `model` and inherit the parent chat model instead of failing sync.

If VS Code also discovers Gentle-AI's Claude-format internal agents, sync marks those managed `sdd-*` and `jd-*` files `user-invocable: false` so the visible picker stays focused on `sdd-orchestrator`.

### Engram (Persistent Memory)

Your AI agent automatically remembers decisions, bugs, and context across sessions. You don't need to do anything -- but when you do:
Expand Down
28 changes: 17 additions & 11 deletions docs/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,19 @@ Kiro uses native custom agents in `~/.kiro/agents/`. `gentle-ai` writes phase ag

## SDD Mode Support

| Feature | Claude Code | OpenCode | Kilo Code | Gemini CLI | Cursor | VS Code Copilot | Codex | Windsurf | Antigravity | Kiro IDE | Qwen Code | OpenClaw | Trae | Pi | Hermes |
| ---------------- | :---------: | :------: | :-------: | :--------: | :----: | :-------------: | :---: | :------: | :---------: | :------: | :-------: | :------: | :--: | :-----: | :----: |
| SDD orchestrator | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Single-mode SDD | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Multi-mode SDD | — | Yes | Yes | — | — | | — | — | — | Yes\* | — | — | — | Yes\*\* | — |
| Feature | Claude Code | OpenCode | Kilo Code | Gemini CLI | Cursor | VS Code Copilot | Codex | Windsurf | Antigravity | Kiro IDE | Qwen Code | OpenClaw | Trae | Pi | Hermes |
| ---------------- | :---------: | :------: | :-------: | :--------: | :----: | :-------------: | :---: | :------: | :---------: | :------: | :-------: | :------: | :--: | :-------: | :----: |
| SDD orchestrator | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Single-mode SDD | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Multi-mode SDD | — | Yes | Yes | — | — | Yes\* | — | — | — | Yes\*\* | — | — | — | Yes\*\*\* | — |

**Multi-mode** (assigning different AI models to each SDD phase) is supported by **OpenCode** and **Kilo Code** through the OpenCode-compatible multi-mode overlay, and by **Kiro IDE** through native subagent `model:` frontmatter. All other agents run in **single-mode** — the orchestrator manages everything using whatever model the agent is already running.
**Multi-mode** (assigning different AI models to each SDD phase) is supported by **OpenCode** and **Kilo Code** through the OpenCode-compatible multi-mode overlay, by **VS Code Copilot** through native `.agent.md` `model` frontmatter, and by **Kiro IDE** through native subagent `model:` frontmatter. All other agents run in **single-mode** — the orchestrator manages everything using whatever model the agent is already running.

> \* **Kiro multi-mode** assigns models per phase through `KiroModelAssignments` (configured via _Configure Models → Configure Kiro models_ in the TUI). The selected Kiro alias (`auto|opus|sonnet|haiku|minimax|glm|deepseek|qwen`) is resolved to a Kiro-native model ID and stamped into each `~/.kiro/agents/sdd-{phase}.md` at sync time.
> \* **VS Code Copilot multi-mode** assigns GitHub Copilot models per coordinator or phase through `VSCodeModelAssignments` (configured via _Configure Models → Configure VS Code Copilot SDD models_ in the TUI). The picker uses dynamic `github-copilot` cache data; missing, invalid, or unvalidated assignments omit `model` so the agent inherits the parent chat model.

> \*\* **Pi multi-mode** is owned by the Pi packages. `gentle-pi` installs SDD agent and chain assets into `.pi/agents/` and `.pi/chains/`; model overrides live in those Pi-managed files or chain steps.
> \*\* **Kiro multi-mode** assigns models per phase through `KiroModelAssignments` (configured via _Configure Models → Configure Kiro models_ in the TUI). The selected Kiro alias (`auto|opus|sonnet|haiku|minimax|glm|deepseek|qwen`) is resolved to a Kiro-native model ID and stamped into each `~/.kiro/agents/sdd-{phase}.md` at sync time.

> \*\*\* **Pi multi-mode** is owned by the Pi packages. `gentle-pi` installs SDD agent and chain assets into `.pi/agents/` and `.pi/chains/`; model overrides live in those Pi-managed files or chain steps.

---

Expand Down Expand Up @@ -128,10 +130,14 @@ Kiro uses native custom agents in `~/.kiro/agents/`. `gentle-ai` writes phase ag

### VS Code Copilot

- Uses the `runSubagent` tool with support for parallel execution
- Skills at `~/.copilot/skills/`
- System prompt at `Code/User/prompts/gentle-ai.instructions.md`
- Native custom agents at `~/.copilot/agents/*.agent.md`: one user-invocable `sdd-orchestrator` coordinator plus hidden SDD phase agents
- Delegates through VS Code Copilot's native sub-agent / `runSubagent` flow
- Skills and shared SDD conventions at `~/.copilot/skills/`
- Global instructions/rules at `Code/User/prompts/gentle-ai.instructions.md`
- MCP config at `Code/User/mcp.json`
- TUI model assignments use dynamic `github-copilot` models and render optional `.agent.md` frontmatter `model`
- Missing, invalid, or unvalidated model assignments omit `model` and inherit the parent chat model; named/suffixed VS Code profiles are not generated
- VS Code may also discover Claude-format agents in `~/.claude/agents`; Gentle-AI marks its managed internal `sdd-*` and `jd-*` Claude agents `user-invocable: false` so the picker stays focused on `sdd-orchestrator`

### Codex

Expand Down
25 changes: 13 additions & 12 deletions docs/intended-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,20 @@ If you want the project-level OpenSpec config convention SDD phases use for conv

---

## Multi-mode SDD (OpenCode SDD Profiles)
## Multi-mode SDD (Model Assignments)

Multi-mode lets you assign different AI models to different SDD phases -- for example, a powerful model for design and a faster one for implementation. This is an OpenCode-exclusive feature, managed through **SDD Profiles**.
Multi-mode lets you assign different AI models to different SDD phases -- for example, a powerful model for design and a faster one for implementation. Most users can ignore this; the default parent model fallback is safe.

For **all other agents** (Claude Code, Cursor, Gemini CLI, VS Code Copilot), SDD runs in single-mode automatically. One model handles everything, and that works perfectly fine.
| Agent | How to configure it |
| ----- | ------------------- |
| OpenCode / Kilo Code | Create an **OpenCode SDD Profile** from the TUI or CLI (`--profile`). Named profiles generate `sdd-orchestrator-{name}` plus suffixed sub-agents. |
| VS Code Copilot | Use _Configure Models → Configure VS Code Copilot SDD models_. Gentle-AI writes one managed coordinator/phase assignment map into native `~/.copilot/agents/*.agent.md` files. |
| Kiro IDE | Use _Configure Models → Configure Kiro models_; aliases are resolved into native Kiro agent `model:` frontmatter. |
| Pi | Use `/gentleman:models`; Pi packages own those assignments. |

If you want multi-mode in OpenCode:
For agents not listed here, SDD runs in single-mode automatically. One model handles everything, and that works perfectly fine.

1. Connect your AI providers in OpenCode first
2. Create a profile via gentle-ai TUI ("OpenCode SDD Profiles") or CLI (`--profile` flag)
3. The base/default SDD conductor is `gentle-orchestrator`
4. Named profiles generate `sdd-orchestrator-{name}` + suffixed sub-agents, each assigned to your chosen model
5. In OpenCode, press **Tab** to switch between `gentle-orchestrator` and custom profiles

You can create multiple profiles (e.g., "cheap" for experimentation, "premium" for production) and switch between them freely.
For VS Code Copilot, the picker uses dynamic `github-copilot` model data. If the cache is missing, the selected model is invalid, or the cost tier cannot be validated, generated agents omit `model` and inherit the parent chat model instead of failing sync. Gentle-AI does not generate named/suffixed VS Code profiles.

If you prefer a **runtime profile manager** that keeps profiles outside `opencode.json`, gentle-ai now supports that too. During sync, OpenCode can auto-detect external profile files under `~/.config/opencode/profiles/*.json` and switch to a safer compatibility path that preserves the active `gentle-orchestrator` prompt instead of overwriting it.

Expand All @@ -95,7 +94,9 @@ This pattern works today on:
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **OpenCode** | Native sub-agent system — each phase is a dedicated agent with its own model, tools, and permissions defined in `opencode.json` |
| **Claude Code** | Via the Agent tool — the orchestrator spawns focused sub-agents and injects resolved project rules |
| **Others** | SDD runs inline (single session) — the model follows the orchestrator instructions without spawning separate agents |
| **Cursor** | Native `.cursor/agents/sdd-{phase}.md` files |
| **VS Code Copilot** | Native `~/.copilot/agents/*.agent.md` files through the sub-agent / `runSubagent` flow |
| **Solo agents** | SDD runs inline (single session) — the model follows the orchestrator instructions without spawning separate agents |

You don't need to configure any of this. The installer sets it up, and the orchestrator manages delegation automatically.

Expand Down
2 changes: 1 addition & 1 deletion docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Release artifacts are produced by CI, but Windows users should install through S
| OpenCode | `%USERPROFILE%\.config\opencode\` |
| Gemini CLI | `%USERPROFILE%\.gemini\` |
| Cursor | `%USERPROFILE%\.cursor\` |
| VS Code Copilot | `%APPDATA%\Code\User\` (settings, MCP, prompts) + `%USERPROFILE%\.copilot\` (skills) |
| VS Code Copilot | `%APPDATA%\Code\User\` (settings, MCP, prompts) + `%USERPROFILE%\.copilot\agents\` (native SDD agents) + `%USERPROFILE%\.copilot\skills\` (skills) |
| Codex | `%USERPROFILE%\.codex\` |
| Windsurf | `%USERPROFILE%\.codeium\windsurf\` (skills, MCP, rules) + `%APPDATA%\Windsurf\User\` (settings) |
| Kimi | `%USERPROFILE%\.kimi\` (includes `config.toml`, system prompt, agents, MCP) |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/charmbracelet/lipgloss v1.1.0
github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.38.0
)

require (
Expand All @@ -28,6 +29,5 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
9 changes: 5 additions & 4 deletions internal/agents/vscode/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func (a *Adapter) InstallCommand(_ system.PlatformProfile) ([][]string, error) {
// VS Code Copilot reads .instructions.md files from the VS Code User prompts folder.
// Skills are loaded from ~/.copilot/skills/ (global), .github/skills/ (workspace),
// ~/.claude/skills/, and .claude/skills/. We target ~/.copilot/skills/ for global reach.
// Native agent files are installed globally under ~/.copilot/agents/.

func (a *Adapter) GlobalConfigDir(homeDir string) string {
return filepath.Join(homeDir, ".copilot")
Expand Down Expand Up @@ -133,15 +134,15 @@ func (a *Adapter) CommandsDir(_ string) string {
}

func (a *Adapter) SupportsSubAgents() bool {
return false
return true
}

func (a *Adapter) SubAgentsDir(_ string) string {
return ""
func (a *Adapter) SubAgentsDir(homeDir string) string {
return filepath.Join(homeDir, ".copilot", "agents")
}

func (a *Adapter) EmbeddedSubAgentsDir() string {
return ""
return "vscode/agents"
}

func (a *Adapter) SupportsSkills() bool {
Expand Down
23 changes: 23 additions & 0 deletions internal/agents/vscode/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vscode
import (
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/gentleman-programming/gentle-ai/internal/model"
Expand Down Expand Up @@ -91,3 +92,25 @@ func TestMCPConfigPathUsesVSCodeUserProfile(t *testing.T) {
}
}
}

func TestSubAgentSupportUsesCopilotUserAgentsDir(t *testing.T) {
a := NewAdapter()
home := "/tmp/home"

if !a.SupportsSubAgents() {
t.Fatal("VS Code adapter must advertise native sub-agent support")
}

got := a.SubAgentsDir(home)
want := filepath.Join(home, ".copilot", "agents")
if got != want {
t.Fatalf("SubAgentsDir() = %q, want %q", got, want)
}
if got == filepath.Join(home, ".github", "agents") || strings.Contains(got, string(filepath.Separator)+".github"+string(filepath.Separator)) {
t.Fatalf("SubAgentsDir() must not target workspace .github/agents, got %q", got)
}

if got := a.EmbeddedSubAgentsDir(); got != "vscode/agents" {
t.Fatalf("EmbeddedSubAgentsDir() = %q, want %q", got, "vscode/agents")
}
}
24 changes: 23 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ func tuiExecute(
CodexCarrilModelAssignments: selection.CodexCarrilModelAssignments,
CodexPhaseModelAssignments: selection.CodexPhaseModelAssignments,
ModelAssignments: modelAssignmentsToState(selection.ModelAssignments),
VSCodeModelAssignments: modelAssignmentsToState(selection.VSCodeModelAssignments),
Persona: string(selection.Persona),
})
}
Expand Down Expand Up @@ -549,6 +550,9 @@ func applyOverrides(selection *model.Selection, overrides *model.SyncOverrides)
if overrides.ModelAssignments != nil {
selection.ModelAssignments = overrides.ModelAssignments
}
if overrides.VSCodeModelAssignments != nil {
selection.VSCodeModelAssignments = overrides.VSCodeModelAssignments
}
if overrides.ClaudeModelAssignments != nil {
selection.ClaudeModelAssignments = overrides.ClaudeModelAssignments
}
Expand Down Expand Up @@ -657,6 +661,9 @@ func loadPersistedAssignments(homeDir string, selection *model.Selection) {
}
selection.ModelAssignments = m
}
if len(selection.VSCodeModelAssignments) == 0 && len(s.VSCodeModelAssignments) > 0 {
selection.VSCodeModelAssignments = stateModelAssignmentsToModel(s.VSCodeModelAssignments)
}
}

// persistAssignments writes the model assignments from selection back to
Expand All @@ -672,10 +679,11 @@ func persistAssignments(homeDir string, selection model.Selection) {
selection.ClaudePhaseAssignments != nil ||
selection.KiroModelAssignments != nil ||
selection.ModelAssignments != nil ||
selection.VSCodeModelAssignments != nil ||
selection.CodexModelAssignments != nil ||
selection.CodexCarrilModelAssignments != nil ||
selection.CodexPhaseModelAssignments != nil
if len(selection.ClaudeModelAssignments) == 0 && len(selection.ClaudePhaseAssignments) == 0 && len(selection.KiroModelAssignments) == 0 && len(selection.ModelAssignments) == 0 && len(selection.CodexModelAssignments) == 0 && len(selection.CodexCarrilModelAssignments) == 0 && len(selection.CodexPhaseModelAssignments) == 0 && !hasAssignmentSignal {
if len(selection.ClaudeModelAssignments) == 0 && len(selection.ClaudePhaseAssignments) == 0 && len(selection.KiroModelAssignments) == 0 && len(selection.ModelAssignments) == 0 && len(selection.VSCodeModelAssignments) == 0 && len(selection.CodexModelAssignments) == 0 && len(selection.CodexCarrilModelAssignments) == 0 && len(selection.CodexPhaseModelAssignments) == 0 && !hasAssignmentSignal {
return
}
current, err := state.Read(homeDir)
Expand Down Expand Up @@ -738,9 +746,23 @@ func persistAssignments(homeDir string, selection model.Selection) {
current.ModelAssignments = nil
}
}
if len(selection.VSCodeModelAssignments) > 0 {
current.VSCodeModelAssignments = modelAssignmentsToState(selection.VSCodeModelAssignments)
}
_ = state.Write(homeDir, current)
}

func stateModelAssignmentsToModel(m map[string]state.ModelAssignmentState) map[string]model.ModelAssignment {
if len(m) == 0 {
return nil
}
out := make(map[string]model.ModelAssignment, len(m))
for k, v := range m {
out[k] = model.ModelAssignment{ProviderID: v.ProviderID, ModelID: v.ModelID, Effort: v.Effort}
}
return out
}

// claudeAliasesToStrings converts a typed ClaudeModelAlias map to plain strings
// for JSON serialisation in state.json.
func claudeAliasesToStrings(m map[string]model.ClaudeModelAlias) map[string]string {
Expand Down
Loading