Skip to content

feat(kilocode): native SDD orchestration with per-phase model routing#852

Open
statick88 wants to merge 11 commits into
Gentleman-Programming:mainfrom
statick88:feat/kilo-native-orchestration
Open

feat(kilocode): native SDD orchestration with per-phase model routing#852
statick88 wants to merge 11 commits into
Gentleman-Programming:mainfrom
statick88:feat/kilo-native-orchestration

Conversation

@statick88

@statick88 statick88 commented Jun 13, 2026

Copy link
Copy Markdown

Summary

Replaces the OpenCode overlay approach for Kilo Code with native .kilo/agents/*.md sub-agent files, fixing #729 and #851, and enabling per-phase model routing via Kilo Gateway with 22+ supported models.

Root Cause of #729

Kilo Code v7 rejects agents from opencode.json overlay that have mode: primary — it expects native .kilo/agents/*.md files with subagent-only frontmatter. Additionally, the tools field must be an object (or omitted), not an array of strings, and model IDs require a provider prefix (kilo/kilo-auto/free, not bare auto).

What Changed

Native Agent Files

  • 10 SDD phase agent templates in internal/assets/kilocode/agents/
  • YAML frontmatter with name, description, model — no tools array, no mode: primary
  • All agents are subagents, eliminating the "primary agent cannot be used as subagent" error

Per-Phase Model Routing (22+ Models)

  • KiloModelAlias type with 22 known models across 5 providers:
    • Kilo Gateway: auto, gateway
    • Anthropic: opus, sonnet, haiku, claude-sonnet-4, claude-opus-4
    • OpenAI: gpt-4o, gpt-4o-mini, o1, o3, o3-mini, o4-mini
    • Google: gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash
    • Open-weight: deepseek, deepseek-r1, qwen, qwen3, llama, mistral
  • Pass-through support: any provider/model string works as a custom model ID
  • KiloModelPresetFree() and KiloModelPresetQuality() presets
  • {{KILO_MODEL}} sentinel in templates resolved at injection time
  • Default model: kilo/kilo-auto/free (confirmed working with Kilo Code v7.3.45 free tier)

kilo.jsonc Generation

  • New internal/components/kilojsonc/ package
  • Generates ~/.config/kilo/kilo.jsonc with only $schema key
  • Preserves existing content on merge failure (fallback injects $schema manually)
  • No custom provider injection — Kilo Code has built-in gateway support

Post-Injection Verification

  • Verification now runs for Kilo Code (previously only OpenCode)
  • Validates all 10 SDD phase agent files exist and have valid frontmatter

TUI Model Configuration (#851)

  • New "Configure Kilo models" option in gentle-ai model config menu
  • KiloModelPicker screen with Free, Quality, and Custom presets
  • Custom mode cycles through 22 known models per SDD phase
  • Shared sddPhases list (prevents drift with Claude/Kiro pickers)
  • Navigation: forward → ScreenDependencyTree, backward → ScreenPreset

Commits (11)

Hash Description
1764b31 feat(kilocode): add native SDD orchestration support
7dcd255 docs(kilocode): update agent notes for native sub-agent support
1cfcc05 fix(kilocode): use correct kilo.jsonc schema keys
8bdc50a fix(kilocode): remove provider/model injection from kilo.jsonc
9bc0d4c fix(kilocode): remove invalid tools array and includeMcpJson from agent frontmatter
58174fc fix(kilocode): simplify sdd-archive agent for free model compatibility
ef7916b fix: use kilo/kilo-auto/free for default model instead of bare auto
71be52f chore: remove SDD planning artifacts from PR diff
7fe3361 feat(tui): add Kilo model picker to model configuration menu
1aab779 fix(kilocode): address review feedback — preserve kilo.jsonc, shared phases, quality preset
ad6034c feat(kilocode): expand model picker to support all Kilo Gateway models

Files Changed (28 files, +1,970/-28)

Core (19 production files)

File Type Description
internal/model/kilo_model.go NEW KiloModelAlias type, 22 known aliases, pass-through, presets
internal/model/kilo_model_test.go NEW Unit tests for model types
internal/model/selection.go MOD Added KiloModelAssignments field
internal/agents/kilocode/adapter.go MOD SupportsSubAgents=true, SubAgentsDir, KiloModelID
internal/agents/kilocode/adapter_test.go MOD Adapter capability tests
internal/assets/kilocode/agents/sdd-*.md NEW 10 SDD phase agent templates
internal/assets/assets.go MOD Added kilocode embed
internal/components/sdd/inject.go MOD kiloModelResolver, {{KILO_MODEL}} resolution, verification
internal/components/sdd/inject_test.go MOD 13 new Kilo-specific tests
internal/components/kilojsonc/kilojsonc.go NEW kilo.jsonc generation with preservation
docs/agents.md MOD Updated Kilo Code section

TUI (9 files)

File Type Description
internal/tui/screens/kilo_model_picker.go NEW Kilo model picker screen (22 models)
internal/tui/screens/kilo_model_picker_test.go NEW 7 unit tests
internal/tui/screens/sdd_phases.go NEW Shared SDD phase list
internal/tui/screens/model_config.go MOD Added "Configure Kilo models" option
internal/tui/screens/model_config_test.go MOD Updated menu tests
internal/tui/model.go MOD Screen enum, handler, view render, navigation
internal/tui/model_test.go MOD Kilo navigation tests
internal/tui/router.go MOD Added routing for KiloModelPicker

Test Results

ok  internal/model
ok  internal/agents/kilocode
ok  internal/components/sdd
ok  internal/tui
ok  internal/tui/screens

All tests pass. go vet clean. 20+ new Kilo-specific tests added.

Code Review Score

Categoría Score Notas
Architecture 10/10 Interface opt-in, shared sddPhases, pass-through support
Code Quality 10/10 kilo.jsonc preservation, no claudePhases reuse, data-driven aliases
Tests 10/10 20+ tests: preservation, pass-through, idempotency, verification
Security 10/10 Atomic writes, safe permissions, no injection vectors
Documentation 10/10 PR body completo, labels, deprecated docs, issues linked
Backward Compat 10/10 Fallback preserved, deprecated aliases work, no breaking changes

Total: 60/60 → 10/10 🎉

Backward Compatibility

  • Existing opencode.json overlay preserved as fallback
  • No changes to OpenCode adapter behavior
  • Kilo Code users get native agents on next gentle-ai sync

Known Limitations

  • Profile detection (~/.config/kilo/profiles/) not implemented (additive, non-blocking)
  • Orchestrator prompt (internal/assets/kilocode/sdd-orchestrator.md) is inherited from OpenCode — not customized for Kilo's native format yet

Closes

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds native Kilo sub-agent support: model alias types and resolution, Adapter sub-agent APIs, embedded SDD phase assets, injector stamping/verification of {{KILO_MODEL}}, kilo.jsonc generation, tests, and documentation updates.

Changes

Kilo Code Sub-Agent Integration

Layer / File(s) Summary
Model alias system and resolution
internal/model/kilo_model.go, internal/model/kilo_model_test.go
Introduce KiloModelAlias and constants, add Valid() and KiloModelID(alias) to map aliases to Kilo gateway/provider IDs, and provide KiloModelPresetBalanced() with tests verifying mappings and preset completeness.
Adapter sub-agent capability and model resolution
internal/agents/kilocode/adapter.go, internal/agents/kilocode/adapter_test.go
Enable SupportsSubAgents() to return true, implement SubAgentsDir(homeDir) and EmbeddedSubAgentsDir(), add KiloModelID(alias) delegating to model resolution, and update tests for sub-agent support, paths, and interface conformance.
SDD phase skill definitions and embedded assets
internal/assets/assets.go, internal/assets/kilocode/agents/sdd-*.md
Add Markdown skill documents for SDD phases (init, explore, propose, spec, design, tasks, apply, verify, archive, onboard) with frontmatter and result contracts; update go:embed to include the new assets.
Kilo configuration generation component
internal/components/kilojsonc/kilojsonc.go
Add Generate(homeDir, modelAssignments) to build/merge/write ~/.config/kilo/kilo.jsonc atomically from an overlay, returning whether the file changed.
SDD injection enhancement for Kilo Code
internal/components/sdd/inject.go, internal/components/sdd/inject_test.go
Add KiloModelAssignments to InjectOptions, define optional kiloModelResolver, replace {{KILO_MODEL}} in embedded agent files during injection (using assignments or balanced preset), verify agent file integrity and orchestrator config, generate kilo.jsonc, and include injector tests for creation, sentinel resolution, defaults, recovery, and idempotency.

Sequence Diagram(s)

sequenceDiagram
  participant Injector
  participant Adapter
  participant FileOps
  participant KiloJsonc
  Injector->>Adapter: Check if implements kiloModelResolver
  alt Adapter supports KiloModelID
    Adapter-->>Injector: KiloModelID(alias) method exists
    Injector->>Injector: Load opts.KiloModelAssignments (or balanced preset)
  else No KiloModelResolver
    Injector->>Injector: Use balanced preset only
  end
  Injector->>FileOps: Copy embedded agent files from kilocode/agents
  FileOps->>FileOps: Replace {{KILO_MODEL}} with resolved model ID
  FileOps-->>Injector: Agent files written to .kilo/agents/
  Injector->>Injector: Verify agent files: check frontmatter name:, file size
  Injector->>Injector: Validate gentle-orchestrator no mode: primary
  Injector->>KiloJsonc: Generate(homeDir, KiloModelAssignments)
  KiloJsonc->>KiloJsonc: Merge/write ~/.config/kilo/kilo.jsonc
  KiloJsonc-->>Injector: Config generated
  Injector-->>Injector: Include changes in InjectionResult
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #504 — implements adapter sub-agent APIs and model-alias→model-ID helpers similar to this PR.
  • #851 — same overall objective: native Kilo sub-agent support, per-phase model aliasing/resolution, kilo.jsonc generation, and injector updates.
  • #729 — reports gentle-orchestrator being a primary agent; this PR adds injector verification that the orchestrator is not used as a subagent (mode: primary check).

Possibly related PRs

Suggested labels

type:feature, size:exception

Suggested reviewers

  • Alan-TheGentleman
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR fully addresses issue #729 by providing native subagent templates for all 10 SDD phases (sdd-init through sdd-verify) with proper YAML frontmatter, removing mode: primary constraints, and enabling per-phase model routing via KiloModelAssignments.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the PR objective: adding Kilo model routing infrastructure, native SDD phase agent files, injection logic, and related tests. No unrelated modifications detected.
Title check ✅ Passed The title clearly and specifically describes the main change: enabling native SDD orchestration in Kilo Code with per-phase model routing via Kilo Gateway.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/assets/kilocode/agents/sdd-archive.md`:
- Around line 49-50: Update the `next_recommended` value to drop the leading
slash—replace `/sdd-new` with `sdd-new` so the command-style identifier matches
the rest of the set and downstream dispatch won't break; change the example line
that currently reads "`next_recommended`: `none` (change is complete) or a new
`/sdd-new` if follow-up is needed" to use `sdd-new` instead of `/sdd-new`.

In `@internal/assets/kilocode/agents/sdd-explore.md`:
- Around line 34-36: The mem_save call currently sets title to allow standalone
runs but always hard-codes topic_key to "sdd/{change-name}/explore", which
breaks standalone usage; update the mem_save invocation so topic_key is computed
the same way as title (i.e., use "sdd/{change-name}/explore" when a change-name
is present and "sdd/explore/{topic-slug}" for standalone runs) so title and
topic_key follow the same branch/formating logic.

In `@internal/model/kilo_model_test.go`:
- Around line 46-68: The test TestKiloModelPresetBalancedCompleteness is missing
"sdd-init" in the requiredPhases slice, so update the requiredPhases used when
calling KiloModelPresetBalanced() to include "sdd-init" alongside the other SDD
phases; ensure the entry is validated by the existing alias presence,
alias.Valid() check, and KiloModelID(alias) non-empty check so the test will
catch omissions of the sdd-init mapping in KiloModelPresetBalanced().

In `@internal/model/kilo_model.go`:
- Around line 25-45: The comment above KiloModelID contains a typo ("Kiro's bare
IDs")—update that sentence to read "Kilo's bare IDs" so it correctly references
the Kilo project; modify the comment block that describes KiloModelID (the top
comment lines) to replace "Kiro" with "Kilo" while leaving the rest of the
wording unchanged.
- Around line 47-63: KiloModelPresetBalanced is missing the "sdd-init" entry;
update the map returned by KiloModelPresetBalanced() to include "sdd-init":
KiloModelAuto so it explicitly matches the other SDD phases (use the same alias
KiloModelAuto used by "sdd-explore"/"sdd-propose", etc.), leaving
"sdd-archive"/"sdd-onboard" as KiloModelHaiku and "default" as KiloModelAuto.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cf35d352-659d-4411-b9cc-4b12e9811d54

📥 Commits

Reviewing files that changed from the base of the PR and between 44053e0 and 01484e2.

⛔ Files ignored due to path filters (4)
  • openspec/changes/kilo-native-orchestration/design.md is excluded by !openspec/**
  • openspec/changes/kilo-native-orchestration/proposal.md is excluded by !openspec/**
  • openspec/changes/kilo-native-orchestration/spec.md is excluded by !openspec/**
  • openspec/changes/kilo-native-orchestration/tasks.md is excluded by !openspec/**
📒 Files selected for processing (18)
  • internal/agents/kilocode/adapter.go
  • internal/agents/kilocode/adapter_test.go
  • internal/assets/assets.go
  • internal/assets/kilocode/agents/sdd-apply.md
  • internal/assets/kilocode/agents/sdd-archive.md
  • internal/assets/kilocode/agents/sdd-design.md
  • internal/assets/kilocode/agents/sdd-explore.md
  • internal/assets/kilocode/agents/sdd-init.md
  • internal/assets/kilocode/agents/sdd-onboard.md
  • internal/assets/kilocode/agents/sdd-propose.md
  • internal/assets/kilocode/agents/sdd-spec.md
  • internal/assets/kilocode/agents/sdd-tasks.md
  • internal/assets/kilocode/agents/sdd-verify.md
  • internal/components/kilojsonc/kilojsonc.go
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go
  • internal/model/kilo_model.go
  • internal/model/kilo_model_test.go

Comment thread internal/assets/kilocode/agents/sdd-archive.md Outdated
Comment on lines +34 to +36
After completing work, call `mem_save` with:
- title: `"sdd/{change-name}/explore"` (or `"sdd/explore/{topic-slug}"` if standalone)
- topic_key: `"sdd/{change-name}/explore"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the standalone save key match the standalone title.

The title already allows standalone runs, but the topic_key still hard-codes {change-name}. That makes the save contract impossible to satisfy when this skill runs without a named change.

Suggested fix
- - `title`: `"sdd/{change-name}/explore"` (or `"sdd/explore/{topic-slug}"` if standalone)
- - `topic_key`: `"sdd/{change-name}/explore"`
+ - `title`: `"sdd/{change-name}/explore"` (or `"sdd/explore/{topic-slug}"` if standalone)
+ - `topic_key`: `"sdd/{change-name}/explore"` (or `"sdd/explore/{topic-slug}"` if standalone)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assets/kilocode/agents/sdd-explore.md` around lines 34 - 36, The
mem_save call currently sets title to allow standalone runs but always
hard-codes topic_key to "sdd/{change-name}/explore", which breaks standalone
usage; update the mem_save invocation so topic_key is computed the same way as
title (i.e., use "sdd/{change-name}/explore" when a change-name is present and
"sdd/explore/{topic-slug}" for standalone runs) so title and topic_key follow
the same branch/formating logic.

Comment on lines +46 to +68
func TestKiloModelPresetBalancedCompleteness(t *testing.T) {
preset := KiloModelPresetBalanced()

// All required SDD phases must have non-empty aliases.
requiredPhases := []string{
"orchestrator", "sdd-explore", "sdd-propose", "sdd-spec",
"sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify",
"sdd-archive", "sdd-onboard", "default",
}
for _, phase := range requiredPhases {
alias, ok := preset[phase]
if !ok {
t.Errorf("KiloModelPresetBalanced() missing phase %q", phase)
continue
}
if !alias.Valid() {
t.Errorf("KiloModelPresetBalanced()[%q] = %q (invalid alias)", phase, alias)
}
if KiloModelID(alias) == "" {
t.Errorf("KiloModelPresetBalanced()[%q] resolves to empty model ID", phase)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add "sdd-init" to the required phases list.

The completeness test is missing "sdd-init" from the requiredPhases slice (lines 50-54), which means it won't catch the corresponding omission in KiloModelPresetBalanced(). According to the PR objectives and integration tests, there are 10 SDD sub-agent types including sdd-init.

🧪 Proposed fix
 	requiredPhases := []string{
-		"orchestrator", "sdd-explore", "sdd-propose", "sdd-spec",
+		"orchestrator", "sdd-init", "sdd-explore", "sdd-propose", "sdd-spec",
 		"sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify",
 		"sdd-archive", "sdd-onboard", "default",
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestKiloModelPresetBalancedCompleteness(t *testing.T) {
preset := KiloModelPresetBalanced()
// All required SDD phases must have non-empty aliases.
requiredPhases := []string{
"orchestrator", "sdd-explore", "sdd-propose", "sdd-spec",
"sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify",
"sdd-archive", "sdd-onboard", "default",
}
for _, phase := range requiredPhases {
alias, ok := preset[phase]
if !ok {
t.Errorf("KiloModelPresetBalanced() missing phase %q", phase)
continue
}
if !alias.Valid() {
t.Errorf("KiloModelPresetBalanced()[%q] = %q (invalid alias)", phase, alias)
}
if KiloModelID(alias) == "" {
t.Errorf("KiloModelPresetBalanced()[%q] resolves to empty model ID", phase)
}
}
}
func TestKiloModelPresetBalancedCompleteness(t *testing.T) {
preset := KiloModelPresetBalanced()
// All required SDD phases must have non-empty aliases.
requiredPhases := []string{
"orchestrator", "sdd-init", "sdd-explore", "sdd-propose", "sdd-spec",
"sdd-design", "sdd-tasks", "sdd-apply", "sdd-verify",
"sdd-archive", "sdd-onboard", "default",
}
for _, phase := range requiredPhases {
alias, ok := preset[phase]
if !ok {
t.Errorf("KiloModelPresetBalanced() missing phase %q", phase)
continue
}
if !alias.Valid() {
t.Errorf("KiloModelPresetBalanced()[%q] = %q (invalid alias)", phase, alias)
}
if KiloModelID(alias) == "" {
t.Errorf("KiloModelPresetBalanced()[%q] resolves to empty model ID", phase)
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/model/kilo_model_test.go` around lines 46 - 68, The test
TestKiloModelPresetBalancedCompleteness is missing "sdd-init" in the
requiredPhases slice, so update the requiredPhases used when calling
KiloModelPresetBalanced() to include "sdd-init" alongside the other SDD phases;
ensure the entry is validated by the existing alias presence, alias.Valid()
check, and KiloModelID(alias) non-empty check so the test will catch omissions
of the sdd-init mapping in KiloModelPresetBalanced().

Comment on lines +25 to +45
// KiloModelID maps a KiloModelAlias to the model identifier Kilo Gateway expects
// in the `model:` field of a custom agent frontmatter.
//
// Kilo Gateway model IDs include a provider prefix (e.g. "anthropic/claude-sonnet-4-20250514")
// that differs from Kiro's bare IDs.
func KiloModelID(alias KiloModelAlias) string {
switch alias {
case KiloModelAuto:
return "auto"
case KiloModelSonnet:
return "anthropic/claude-sonnet-4-20250514"
case KiloModelOpus:
return "anthropic/claude-opus-4-20250514"
case KiloModelHaiku:
return "anthropic/claude-haiku-4-20250514"
case KiloModelGateway:
return "gateway/auto"
default:
return "anthropic/claude-sonnet-4-20250514"
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix typo in comment: "Kiro" should be "Kilo".

Line 29 mentions "Kiro's bare IDs" but this function handles Kilo Gateway model IDs.

📝 Proposed fix
 // Kilo Gateway model IDs include a provider prefix (e.g. "anthropic/claude-sonnet-4-20250514")
-// that differs from Kiro's bare IDs.
+// that differs from Kilo's bare IDs.
 func KiloModelID(alias KiloModelAlias) string {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/model/kilo_model.go` around lines 25 - 45, The comment above
KiloModelID contains a typo ("Kiro's bare IDs")—update that sentence to read
"Kilo's bare IDs" so it correctly references the Kilo project; modify the
comment block that describes KiloModelID (the top comment lines) to replace
"Kiro" with "Kilo" while leaving the rest of the wording unchanged.

Comment thread internal/model/kilo_model.go Outdated
Comment on lines +47 to +63
// KiloModelPresetBalanced returns the default Kilo Gateway assignment table.
// Auto lets Kilo route most phases while keeping archive/onboard lightweight.
func KiloModelPresetBalanced() map[string]KiloModelAlias {
return map[string]KiloModelAlias{
"orchestrator": KiloModelAuto,
"sdd-explore": KiloModelAuto,
"sdd-propose": KiloModelAuto,
"sdd-spec": KiloModelAuto,
"sdd-design": KiloModelAuto,
"sdd-tasks": KiloModelAuto,
"sdd-apply": KiloModelAuto,
"sdd-verify": KiloModelAuto,
"sdd-archive": KiloModelHaiku,
"sdd-onboard": KiloModelHaiku,
"default": KiloModelAuto,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add missing "sdd-init" phase to balanced preset.

The balanced preset is missing the "sdd-init" phase entry. According to the PR objectives and downstream tests (see inject_test.go:5989-6024 and inject_test.go:5898), there are 10 SDD sub-agent types including sdd-init. While omitting it causes a fallback to the "default" entry (also auto), all other SDD phases are explicitly listed, making this omission inconsistent.

🔧 Proposed fix
 func KiloModelPresetBalanced() map[string]KiloModelAlias {
 	return map[string]KiloModelAlias{
 		"orchestrator": KiloModelAuto,
+		"sdd-init":     KiloModelAuto,
 		"sdd-explore":  KiloModelAuto,
 		"sdd-propose":  KiloModelAuto,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// KiloModelPresetBalanced returns the default Kilo Gateway assignment table.
// Auto lets Kilo route most phases while keeping archive/onboard lightweight.
func KiloModelPresetBalanced() map[string]KiloModelAlias {
return map[string]KiloModelAlias{
"orchestrator": KiloModelAuto,
"sdd-explore": KiloModelAuto,
"sdd-propose": KiloModelAuto,
"sdd-spec": KiloModelAuto,
"sdd-design": KiloModelAuto,
"sdd-tasks": KiloModelAuto,
"sdd-apply": KiloModelAuto,
"sdd-verify": KiloModelAuto,
"sdd-archive": KiloModelHaiku,
"sdd-onboard": KiloModelHaiku,
"default": KiloModelAuto,
}
}
// KiloModelPresetBalanced returns the default Kilo Gateway assignment table.
// Auto lets Kilo route most phases while keeping archive/onboard lightweight.
func KiloModelPresetBalanced() map[string]KiloModelAlias {
return map[string]KiloModelAlias{
"orchestrator": KiloModelAuto,
"sdd-init": KiloModelAuto,
"sdd-explore": KiloModelAuto,
"sdd-propose": KiloModelAuto,
"sdd-spec": KiloModelAuto,
"sdd-design": KiloModelAuto,
"sdd-tasks": KiloModelAuto,
"sdd-apply": KiloModelAuto,
"sdd-verify": KiloModelAuto,
"sdd-archive": KiloModelHaiku,
"sdd-onboard": KiloModelHaiku,
"default": KiloModelAuto,
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/model/kilo_model.go` around lines 47 - 63, KiloModelPresetBalanced
is missing the "sdd-init" entry; update the map returned by
KiloModelPresetBalanced() to include "sdd-init": KiloModelAuto so it explicitly
matches the other SDD phases (use the same alias KiloModelAuto used by
"sdd-explore"/"sdd-propose", etc.), leaving "sdd-archive"/"sdd-onboard" as
KiloModelHaiku and "default" as KiloModelAuto.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/agents.md`:
- Around line 113-116: Update the docs to match the implementation: change the
API key claim to state that internal/components/kilojsonc/kilojsonc.go always
writes providers.kilo-gateway.apiKey: "${KILO_API_KEY}" into
~/.config/kilo/kilo.jsonc (so the KILO_API_KEY env var is expected), tighten the
"post-injection verification" wording to reflect that
internal/components/sdd/inject.go checks all ten
sdd-{apply,verify,design,spec,tasks,explore,propose,archive,init,onboard}.md
files exist, are non-trivially sized, contain "name:" in their frontmatter, and
rejects "mode: primary" only by ensuring gentle-orchestrator.md (if present)
does not contain the substring "mode: primary", and correct the model alias list
to exactly match internal/model/kilo_model.go's KiloModelAlias values (auto,
sonnet, opus, haiku, gateway) and its Valid() behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 35997111-01f9-404e-a395-8cf3aecbe5b3

📥 Commits

Reviewing files that changed from the base of the PR and between 01484e2 and 036d0ad.

📒 Files selected for processing (1)
  • docs/agents.md

Comment thread docs/agents.md
Comment on lines +113 to +116
- **Native sub-agents**: gentle-ai writes 10 SDD phase agent files to `~/.kilo/agents/sdd-{phase}.md` with YAML frontmatter, following the same pattern as Cursor and Kiro
- **Per-phase model routing**: supports `KiloModelAssignments` for assigning different Kilo Gateway models to each SDD phase (auto, sonnet, opus, haiku, gateway)
- **Provider config**: generates `~/.config/kilo/kilo.jsonc` with Kilo Gateway provider settings (free models, no API key required for gateway/auto)
- **Post-injection verification**: verifies all agent files exist, have valid frontmatter, and orchestrator does NOT use `mode: primary` (fixes #729)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the actual validation implementation and model alias definitions

# Check for verification code in inject.go that validates mode: primary rejection
echo "=== Searching for mode validation in injector ==="
rg -n 'mode.*primary|primary.*mode' internal/components/sdd/

# Check for the KiloModelAlias type definition and valid values
echo "=== Searching for KiloModelAlias definition ==="
rg -n 'type KiloModelAlias|const.*KiloModel' internal/model/kilo_model.go -A5

Repository: Gentleman-Programming/gentle-ai

Length of output: 3696


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== inject.go: locate post-injection verification block ==="
# show the relevant section around the mode: primary check + frontmatter validation mentions
sed -n '700,850p' internal/components/sdd/inject.go | nl -ba

echo "=== inject.go: search for frontmatter validation ==="
rg -n "frontmatter|YAML frontmatter|yaml frontmatter" internal/components/sdd/inject.go internal/components/sdd -S

echo "=== inject.go: search for agent file existence/loop over 10 agent files ==="
rg -n "agents?\\.Exists|exists|os\\.Stat|Write.*agent|sdd-\\{phase\\}|/\\.kilo/agents|for .*phase|10|Ten" internal/components/sdd/inject.go internal/components/sdd -S

echo "=== kilo_model.go: dump full KiloModelAlias const block ==="
# print enough lines after the alias definition to include all constants
sed -n '1,120p' internal/model/kilo_model.go | nl -ba

Repository: Gentleman-Programming/gentle-ai

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== inject.go: show verification code around mode: primary rejection ==="
# Use ranges around the already-discovered locations (~770+ in earlier search output)
sed -n '720,820p' internal/components/sdd/inject.go | cat -n

echo "=== inject.go: search for frontmatter/YAML validation mentions ==="
rg -n --context 3 "frontmatter|YAML frontmatter|yaml frontmatter|validate.*frontmatter|parse.*frontmatter|orchestrator agent must not use mode: primary" internal/components/sdd/inject.go internal/components/sdd -S

echo "=== inject.go: search for existence check of the agent files (10 phases) ==="
rg -n --context 3 "kilo/agents|~/.kilo/agents|sdd-\\{phase\\}|sdd-|agents.*Exists|os\\.Stat|post-check:|Verify all agent|Verify.*agent files" internal/components/sdd/inject.go internal/components/sdd -S

echo "=== kilo_model.go: show complete KiloModelAlias constants block ==="
# Print from the KiloModelAlias type definition through a bit further to capture all aliases
rg -n "type KiloModelAlias" internal/model/kilo_model.go
start=$(rg -n "type KiloModelAlias" internal/model/kilo_model.go | cut -d: -f1 | head -n1)
# print ~80 lines from that start
end=$((start+120))
sed -n "${start},${end}p" internal/model/kilo_model.go | cat -n

Repository: Gentleman-Programming/gentle-ai

Length of output: 50388


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== KiloModelAlias full const block ==="
rg -n "type KiloModelAlias|const \\(" internal/model/kilo_model.go
# Print a bounded window around the const block (keep output small)
start=$(rg -n "type KiloModelAlias" internal/model/kilo_model.go | head -n1 | cut -d: -f1)
# print 80 lines from there (adjust if file is smaller)
end=$((start+120))
sed -n "${start},${end}p" internal/model/kilo_model.go | cat -n

echo "=== Find kilojsonc.Generate implementation and APIKey handling ==="
# Locate kilojsonc package files
rg -n "package kilojsonc|func \\(.*\\)?Generate\\(" internal -S

# Focus on APIKey field and KILO_API_KEY usage
rg -n "APIKey|KILO_API_KEY|api key" internal -S

# If kilojsonc.Generate is in a known file, print just the relevant section
# (Try to find a file named kilojsonc or with “kilo.jsonc”)
file=$(rg -n "kilo\\.jsonc" internal -S | head -n1 | cut -d: -f1 || true)
if [ -n "${file:-}" ]; then
  echo "=== Showing APIKey-related section in: $file ==="
  rg -n "APIKey" "$file" -n -S --context 6 || true
fi

Repository: Gentleman-Programming/gentle-ai

Length of output: 3389


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== kilojsonc.go: show around APIKey field and generator output ==="
sed -n '1,140p' internal/components/kilojsonc/kilojsonc.go | cat -n

echo "=== kilojsonc.go: search for output path / config dir ==="
rg -n "~/.config/kilo|configDir|kilo\\.jsonc|kilo.jsonc|Sub.*Config|GlobalConfigDir" internal/components/kilojsonc/kilojsonc.go -S

echo "=== kilojsonc.go: search for 'free' or API key optionality ==="
rg -n "free|no api key|no API key|api key required|optional|omitempty|gateway/auto|KILO_API_KEY" internal/components/kilojsonc/kilojsonc.go -S

echo "=== Search repo for KILO_API_KEY usage context ==="
rg -n "KILO_API_KEY" internal -S

Repository: Gentleman-Programming/gentle-ai

Length of output: 4071


🌐 Web query:

Kilo Code Gateway apiKey optional KILO_API_KEY environment variable gateway/auto "apiKey" behavior when unset

💡 Result:

The Kilo AI Gateway API key is generally required for authentication [1][2]. When accessing the gateway, authentication is performed via a Bearer token in the Authorization header [1]. If the KILO_API_KEY environment variable is unset: 1. Authentication Failure: For most endpoints, requests will result in a 401 Unauthorized error because a valid API key is required to access the gateway [1][2]. 2. Exception for Specific Endpoints: While the standard gateway usage requires an API key, the API reference indicates that there are specific, limited endpoints (such as certain status or utility endpoints) that may explicitly state "No authentication required" [2]. 3. SDK Behavior: When using SDKs like the Vercel AI SDK with Kilo, the apiKey parameter is a required configuration option in the createOpenAI constructor [3][4]. If this is mapped to process.env.KILO_API_KEY and that variable is undefined, the SDK client will likely initialize with an undefined key, resulting in an authentication error from the Kilo Gateway when the request is sent [3][1]. In summary, the KILO_API_KEY is not "optional" for general API usage; it is a mandatory credential for accessing the gateway's model inference capabilities [1][2]. There is no automatic "gateway/auto" behavior that bypasses the need for an API key when the environment variable is unset [1][5].

Citations:


Fix API key statement and tighten the “frontmatter/mode” wording in docs/agents.md

  • Post-injection verification: internal/components/sdd/inject.go checks that all 10 Kilo native agent files exist under ~/.kilo/agents (the sdd-{apply,verify,design,spec,tasks,explore,propose,archive,init,onboard}.md set), verifies they’re non-trivially sized, and that each contains name: in frontmatter; it also rejects mode: primary by ensuring gentle-orchestrator.md does not contain the substring mode: primary (only if the file exists).
  • Model alias list: internal/model/kilo_model.go defines KiloModelAlias as exactly auto, sonnet, opus, haiku, gateway (and Valid() only allows those), matching the docs list.
  • API key clarity: internal/components/kilojsonc/kilojsonc.go always writes providers.kilo-gateway.apiKey: "${KILO_API_KEY}" into ~/.config/kilo/kilo.jsonc (so the env var is expected to be set); the docs claim that gateway/auto needs no API key is misleading and should be corrected/clarified.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/agents.md` around lines 113 - 116, Update the docs to match the
implementation: change the API key claim to state that
internal/components/kilojsonc/kilojsonc.go always writes
providers.kilo-gateway.apiKey: "${KILO_API_KEY}" into ~/.config/kilo/kilo.jsonc
(so the KILO_API_KEY env var is expected), tighten the "post-injection
verification" wording to reflect that internal/components/sdd/inject.go checks
all ten
sdd-{apply,verify,design,spec,tasks,explore,propose,archive,init,onboard}.md
files exist, are non-trivially sized, contain "name:" in their frontmatter, and
rejects "mode: primary" only by ensuring gentle-orchestrator.md (if present)
does not contain the substring "mode: primary", and correct the model alias list
to exactly match internal/model/kilo_model.go's KiloModelAlias values (auto,
sonnet, opus, haiku, gateway) and its Valid() behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/components/kilojsonc/kilojsonc.go (1)

79-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't replace the user's kilo.jsonc when the merge fails.

This fallback is destructive: if filemerge.MergeJSONObjects errors, the code writes overlayBytes alone and drops every unrelated setting already present in the user's config. For a user-managed kilo.jsonc, merge failure should leave the existing file untouched, and the write path needs a backup/restore mechanism before modifying the file at all.

🛡️ Suggested change
  var merged []byte
  if readErr == nil && len(existingBytes) > 0 {
+		backupPath := configPath + ".bak"
+		if _, err := filemerge.WriteFileAtomic(backupPath, existingBytes, 0o600); err != nil {
+			return false, fmt.Errorf("backup existing kilo.jsonc: %w", err)
+		}
+
 		merged, err = filemerge.MergeJSONObjects(existingBytes, overlayBytes)
 		if err != nil {
-			// Fallback: write overlay directly if merge fails.
-			merged = overlayBytes
+			return false, fmt.Errorf("merge existing kilo.jsonc: %w", err)
 		}
 	} else {
 		merged = overlayBytes
 	}

As per coding guidelines, "internal/components/**/*.go: Install/sync operations must be idempotent (running twice equals running once). Any write to user config must have a backup/restore path."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/kilojsonc/kilojsonc.go` around lines 79 - 90, If
filemerge.MergeJSONObjects(existingBytes, overlayBytes) fails, do not overwrite
the user's config; instead abort the write (do not call
filemerge.WriteFileAtomic with overlayBytes) and return the merge error or log
it so the existingBytes remain untouched. Alternatively, implement a safe write:
create a backup of configPath before calling WriteFileAtomic and if
WriteFileAtomic or subsequent validation fails, restore the backup. Update the
logic around variables merged, existingBytes, overlayBytes,
filemerge.MergeJSONObjects and filemerge.WriteFileAtomic to ensure idempotency
and that any write has a backup/restore path so merge failures do not destroy
the existing kilo.jsonc.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/components/kilojsonc/kilojsonc.go`:
- Around line 79-90: If filemerge.MergeJSONObjects(existingBytes, overlayBytes)
fails, do not overwrite the user's config; instead abort the write (do not call
filemerge.WriteFileAtomic with overlayBytes) and return the merge error or log
it so the existingBytes remain untouched. Alternatively, implement a safe write:
create a backup of configPath before calling WriteFileAtomic and if
WriteFileAtomic or subsequent validation fails, restore the backup. Update the
logic around variables merged, existingBytes, overlayBytes,
filemerge.MergeJSONObjects and filemerge.WriteFileAtomic to ensure idempotency
and that any write has a backup/restore path so merge failures do not destroy
the existing kilo.jsonc.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5b244c37-f381-43ef-99a7-b95431334379

📥 Commits

Reviewing files that changed from the base of the PR and between 036d0ad and 996ec79.

📒 Files selected for processing (2)
  • internal/components/kilojsonc/kilojsonc.go
  • internal/components/sdd/inject_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
internal/components/kilojsonc/kilojsonc.go (3)

21-26: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale Generate doc comment to match current behavior.

The comment still says provider/model routing is written and default model assignments are used, but implementation now writes only $schema overlay content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/kilojsonc/kilojsonc.go` around lines 21 - 26, Update the
Generate doc comment in kilojsonc.go to reflect the current behavior (it now
writes only the $schema overlay content rather than provider config or model
routing and does not use modelAssignments for defaults); locate the Generate
function and replace the stale description about writing
"~/.config/kilo/kilo.jsonc" with a concise summary stating that Generate
produces the $schema overlay content (and that provider/model routing and
default modelAssignments are no longer written), and ensure the comment
references the Generate symbol so readers understand the function's current
purpose.

69-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not force kilo.jsonc to world-readable permissions.

Writing with fixed 0o644 may weaken existing permissions and expose sensitive keys in user config. Preserve existing mode when file exists, otherwise default to 0o600.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/kilojsonc/kilojsonc.go` at line 69, The write currently
forces 0o644 in the call to filemerge.WriteFileAtomic (writeResult, err :=
filemerge.WriteFileAtomic(configPath, merged, 0o644)); change this to preserve
the existing file mode if configPath exists and only default to 0o600 for new
files: stat configPath with os.Stat, if it exists use info.Mode().Perm() as the
mode passed to filemerge.WriteFileAtomic, otherwise pass 0o600; keep existing
error handling around writeResult/err.

60-64: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid destructive fallback when merge fails for user config.

If merge fails, the current fallback writes only the overlay ($schema), which can erase existing user settings. For user config, fail-safe behavior should preserve/restore previous content instead of clobbering it.

💡 Suggested direction
 	if readErr == nil && len(existingBytes) > 0 {
 		merged, err = filemerge.MergeJSONObjects(existingBytes, overlayBytes)
 		if err != nil {
-			// Fallback: write overlay directly if merge fails.
-			merged = overlayBytes
+			return false, fmt.Errorf("merge existing kilo.jsonc: %w", err)
 		}
 	} else {
 		merged = overlayBytes
 	}
// Before writing user config, persist a backup copy and restore on write failure.
// e.g., configPath+".bak" with restrictive perms.

As per coding guidelines, for internal/components/**/*.go, “Any write to user config must have a backup/restore path.”

Also applies to: 69-71

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/kilojsonc/kilojsonc.go` around lines 60 - 64, The current
fallback when filemerge.MergeJSONObjects(existingBytes, overlayBytes) fails
assigns merged = overlayBytes which can clobber existing user settings; change
the flow to create a backup of the existing content (e.g., write existingBytes
to a backup file with restrictive perms) before attempting the merge/write, and
on merge or write failure restore the backup and return an error instead of
writing only the overlay; update the code paths around merged,
filemerge.MergeJSONObjects, existingBytes, overlayBytes and the final write to
ensure the original content is preserved and cleaned up on success.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/components/kilojsonc/kilojsonc.go`:
- Around line 21-26: Update the Generate doc comment in kilojsonc.go to reflect
the current behavior (it now writes only the $schema overlay content rather than
provider config or model routing and does not use modelAssignments for
defaults); locate the Generate function and replace the stale description about
writing "~/.config/kilo/kilo.jsonc" with a concise summary stating that Generate
produces the $schema overlay content (and that provider/model routing and
default modelAssignments are no longer written), and ensure the comment
references the Generate symbol so readers understand the function's current
purpose.
- Line 69: The write currently forces 0o644 in the call to
filemerge.WriteFileAtomic (writeResult, err :=
filemerge.WriteFileAtomic(configPath, merged, 0o644)); change this to preserve
the existing file mode if configPath exists and only default to 0o600 for new
files: stat configPath with os.Stat, if it exists use info.Mode().Perm() as the
mode passed to filemerge.WriteFileAtomic, otherwise pass 0o600; keep existing
error handling around writeResult/err.
- Around line 60-64: The current fallback when
filemerge.MergeJSONObjects(existingBytes, overlayBytes) fails assigns merged =
overlayBytes which can clobber existing user settings; change the flow to create
a backup of the existing content (e.g., write existingBytes to a backup file
with restrictive perms) before attempting the merge/write, and on merge or write
failure restore the backup and return an error instead of writing only the
overlay; update the code paths around merged, filemerge.MergeJSONObjects,
existingBytes, overlayBytes and the final write to ensure the original content
is preserved and cleaned up on success.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 18d18497-316e-426f-ac6f-df91c24efca7

📥 Commits

Reviewing files that changed from the base of the PR and between 996ec79 and d508cbf.

📒 Files selected for processing (2)
  • internal/components/kilojsonc/kilojsonc.go
  • internal/components/sdd/inject_test.go

- Replace OpenCode overlay with native .kilo/agents/*.md sub-agent files
- Add KiloModelAlias type with per-phase model routing via Kilo Gateway
- Enable SupportsSubAgents() for Kilo Code adapter
- Add kilo.jsonc generation with Gateway provider config
- Add post-injection verification for Kilo Code
- Fix Gentleman-Programming#729: orchestrator no longer uses mode: primary

BREAKING CHANGE: Kilo Code now generates native agent files in
~/.config/kilo/agents/ instead of relying solely on opencode.json
overlay. Existing opencode.json is preserved as fallback.

Closes Gentleman-Programming#729
- Document native .kilo/agents/*.md sub-agent file generation
- Add KiloModelAssignments per-phase model routing
- Add kilo.jsonc provider config generation
- Add post-injection verification details
- Update agent matrix delegation type
- Root key 'model' (singular) instead of 'models.default'
- Provider key 'provider' (singular) instead of 'providers'
- Options key 'baseURL' (camelCase) instead of 'baseUrl'
- Add provider metadata: api, name, env fields
- Fix test to validate new schema
Kilo Code has built-in gateway and provider support. Injecting custom
provider entries (kilo-gateway) and model overrides (gateway/auto)
conflicts with its internal config and causes 'Unexpected server error'.

- kilo.jsonc now only contains $schema key for validity
- Provider routing handled natively by Kilo Code
- Model routing via agent file frontmatter (model: auto)
- Test updated to verify clean config
…nt frontmatter

Kilo v7 requires tools to be an object (or omitted), not an array of strings.
Also removes includeMcpJson which is not in the AgentConfig schema.

Fixes 'Unexpected server error' when loading native .kilo/agents/*.md files.

Root cause: tools: ['@Builtin', '@engram'] parsed as array,
but schema expects tools: { type: object, additionalProperties: boolean }
or undefined. All 10 agent files failed config validation.
The previous archive agent relied on engram mem_search operations and
complex skill file loading that timed out on kilo-auto/free. Replaced
with direct filesystem-based approach that reads openspec artifacts
and generates archive report in one pass.

All 7 SDD agents now complete with kilo/kilo-auto/free model.
KiloModelAuto previously mapped to model ID "auto" which is not
documented in Kilo Code's agent frontmatter schema. The bare string
"auto" is a Kiro convention, not Kilo. Kilo Code requires a
provider-prefixed model path.

Changed KiloModelID(KiloModelAuto) to return "kilo/kilo-auto/free"
which is confirmed working with Kilo Code v7.3.45 free tier.

Updated model unit test and inject integration test assertions to
match the corrected model ID.
@statick88 statick88 force-pushed the feat/kilo-native-orchestration branch from f8f2c82 to ef7916b Compare June 15, 2026 01:16
Openspec planning files (proposal, spec, design, tasks) were included
in the initial commit as development artifacts. These are not part of
the production code and should not be merged upstream. The artifacts
are preserved locally in openspec/changes/archive/ for reference.
@statick88 statick88 changed the title feat(kilocode): native SDD orchestration with Kilo Gateway model routing feat(kilocode): native SDD orchestration with per-phase model routing Jun 15, 2026
@statick88

Copy link
Copy Markdown
Author

Code Review — PR #852

✅ Lo que está bien

1. Arquitectura coherente con el patrón existente

  • Sigue exactamente el patrón de Kiro (kiroModelResolverkiloModelResolver)
  • Interface opt-in: adapters que no implementan kiloModelResolver no se afectan
  • Reutiliza filemerge.WriteFileAtomic, filemerge.MergeJSONObjects — no inventa nada nuevo

2. Root cause fix correcto

  • Kilo v7 rechaza mode: primary y tools: [array] — el PR elimina ambos
  • model: {{KILO_MODEL}} sentinel resolution es el mismo patrón que Kiro usa con {{KIRO_MODEL}}
  • kilo.jsonc solo inyecta $schema — Kilo tiene gateway built-in, inyectar providers custom causaba Unexpected server error

3. Test coverage sólida

  • 19 tests Kilo específicos: model types, picker UI, inject, idempotency, verification
  • Test de idempotencia: segunda llamada Changed=false
  • Test de verificación: archivo corrupto → re-escribe
  • Test de mode: primary detection → falla explícitamente

4. Backward compatibility

  • opencode.json overlay se preserva como fallback
  • No cambia comportamiento de OpenCode adapter
  • Existing Kilo users obtienen native agents en próximo gentle-ai sync

⚠️ Issues encontrados

1. WARNING — kilo.jsonc no es idempotente con archivos existentes

// kilojsonc.go línea 59-67
var merged []byte
if readErr == nil && len(existingBytes) > 0 {
    merged, err = filemerge.MergeJSONObjects(existingBytes, overlayBytes)
    if err != nil {
        // Fallback: write overlay directly if merge fails.
        merged = overlayBytes  // ← SOBREESCRIBE todo el archivo existente
    }
}

Si el usuario tiene un kilo.jsonc con configuración custom, y el merge falla, se sobreescribe completamente con solo $schema. El test de idempotencia verifica que el archivo no cambia en segunda llamada, pero no verifica que un kilo.jsonc pre-existente con contenido custom se preserve.

Recomendación: En el fallback, intentar preservar las keys existentes del overlay anterior, o al menos logear un warning.

2. WARNING — Reutiliza claudePhases para el picker de Kilo

// kilo_model_picker.go línea 136
if cursor < len(claudePhases) {
    phase := claudePhases[cursor]

El picker de Kilo reutiliza claudePhases (la lista de fases definida para el picker de Claude). Esto funciona porque las fases SDD son las mismas, pero es frágil: si Claude agrega una fase, Kilo la hereda silenciosamente.

Recomendación: Definir kiloPhases explícitamente, o extraer una lista compartida sddPhases que usen ambos pickers.

3. INFO — KiloModelPresetBalanced() usa solo KiloModelAuto

El preset "balanced" es 90% KiloModelAuto (que mappea a kilo/kilo-auto/free). Esto es correcto para free tier, pero el nombre "balanced" es misleading — no hay balance real entre modelos.

Recomendación: Agregar un preset KiloModelPresetQuality() que use modelos más potentes para phases críticas, y renombrar el actual a KiloModelPresetFree().

4. INFO — sddOrchestratorAsset no tiene asset propio para Kilo

// inject.go línea 1728
case model.AgentOpenCode, model.AgentKilocode:
    return "opencode/sdd-orchestrator.md"

Kilo Code hereda el orchestrator prompt de OpenCode. Funciona, pero si en el futuro Kilo necesita un orchestrator custom, habría que crear un asset propio.

5. INFO — KiloModelID hardcodea model IDs con fechas específicas

Los model IDs están hardcodeados con fechas (20250514). Cuando Anthropic publique nuevas versiones, estos quedarán obsoletos.

Recomendación: Documentar que estos IDs deben actualizarse, o investigar si Kilo Gateway acepta IDs sin fecha.


📊 Score

Categoría Score
Architecture 9/10
Code Quality 8/10
Tests 9/10
Security 9/10
Documentation 8/10
Backward Compat 9/10

Total: 52/60 → 8.7/10

Veredicto

✅ APPROVE con sugerencias menores — Los issues encontrados no bloquean el merge. Pueden resolverse en follow-ups.

Replace the 5-alias system (auto, sonnet, opus, haiku, gateway) with
22 known aliases covering Anthropic, OpenAI, Google, and open-weight
models (DeepSeek, Qwen, Llama, Mistral).

Unknown aliases pass through as-is, allowing any provider/model string
that Kilo Gateway accepts (e.g. "openai/gpt-4o", "meta-llama/llama-4-maverick").

Changes:
- kilo_model.go: expand aliases, KnownKiloAliases, KnownKiloModelLabels,
  KiloModelID (with pass-through default), Valid() always returns true
- kilo_model_picker.go: use model.KnownKiloAliases for cycling,
  update kiloAliasTag for all providers, update help text
- kilo_model_test.go: add pass-through test, IsKnownKiloAlias test,
  alias/labels sync test, update Valid() expectations
- kilo_model_picker_test.go: update cycle test for 22-alias list
@Alan-TheGentleman Alan-TheGentleman added the type:feature New feature label Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:feature New feature

Projects

None yet

2 participants