Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/reference/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ opencode: # for OpenCode-specific parameters
permission:
bash:
"git diff": allow
kilo: # for Kilo-specific parameters
mode: all # (optional, defaults to "all") use "subagent" for hidden/subagent-only agents
takt: # takt specific parameters (optional; emitted under .takt/facets/personas/)
name: "renamed-stem" # (optional) override the emitted filename stem (no path separators or "..")
---
Expand All @@ -248,6 +250,8 @@ Attention, again, you are just the planner, so though you can read any files and

> **Gemini CLI note (as of 2026-04-01):** Subagents are generated to `.gemini/agents/`. To enable the agents feature, set `"experimental": { "enableAgents": true }` in your `.gemini/settings.json`.

> **Kilo note (as of 2026-05-13):** Kilo's documented default for user-defined agents is `mode: all`, which makes the agent available both as a top-level pick and as a subagent. Set `kilo.mode: subagent` to opt into hidden/subagent-only behavior.

## `.rulesync/skills/*/SKILL.md`

Example:
Expand Down
4 changes: 4 additions & 0 deletions skills/rulesync/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ opencode: # for OpenCode-specific parameters
permission:
bash:
"git diff": allow
kilo: # for Kilo-specific parameters
mode: all # (optional, defaults to "all") use "subagent" for hidden/subagent-only agents
takt: # takt specific parameters (optional; emitted under .takt/facets/personas/)
name: "renamed-stem" # (optional) override the emitted filename stem (no path separators or "..")
---
Expand All @@ -248,6 +250,8 @@ Attention, again, you are just the planner, so though you can read any files and

> **Gemini CLI note (as of 2026-04-01):** Subagents are generated to `.gemini/agents/`. To enable the agents feature, set `"experimental": { "enableAgents": true }` in your `.gemini/settings.json`.

> **Kilo note (as of 2026-05-13):** Kilo's documented default for user-defined agents is `mode: all`, which makes the agent available both as a top-level pick and as a subagent. Set `kilo.mode: subagent` to opt into hidden/subagent-only behavior.

## `.rulesync/skills/*/SKILL.md`

Example:
Expand Down
2 changes: 1 addition & 1 deletion src/e2e/e2e-subagents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ You are a primary agent. You appear in the Tab rotation.
const testDir = getTestDir();

// Kilo's documented default for user-defined agents is `all`
// (https://kilocode.ai/docs/customize/custom-modes). Rulesync must
// (https://kilo.ai/docs/customize/custom-modes). Rulesync must
// emit `mode: all` when source frontmatter has no `kilo.mode`,
// otherwise generated agents are hidden from Kilo's agent picker.
const subagentContent = `---
Expand Down
17 changes: 9 additions & 8 deletions src/features/subagents/kilo-subagent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ describe("KiloSubagent", () => {
it("should build Kilo subagent with default mode 'all' when not specified", () => {
// Kilo's documented default for user-defined agents is `all` (available
// both as a top-level pick AND as a subagent). See:
// https://kilocode.ai/docs/customize/custom-modes
// https://kilo.ai/docs/customize/custom-modes
// Previously rulesync defaulted to "subagent", which hid generated
// agents from the Kilo agent picker.
const rulesyncSubagent = new RulesyncSubagent({
Expand Down Expand Up @@ -165,10 +165,7 @@ describe("KiloSubagent", () => {
expect(toolSubagent.getFrontmatter().mode).toBe("subagent");
});

it("should honour explicit kilo.mode = 'all' override (matches default)", () => {
// Symmetry test: explicit `kilo.mode: all` matches the new default but
// is a valid explicit value. Guards against any future regression
// where the defaulting logic accidentally overrides user intent.
it("should preserve explicit kilo.mode = 'all' with other Kilo-specific fields", () => {
const rulesyncSubagent = new RulesyncSubagent({
outputRoot: testDir,
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
Expand All @@ -179,6 +176,7 @@ describe("KiloSubagent", () => {
description: "Explicitly all mode",
kilo: {
mode: "all",
temperature: 0.4,
},
},
body: "Body",
Expand All @@ -192,7 +190,10 @@ describe("KiloSubagent", () => {
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
}) as KiloSubagent;

expect(toolSubagent.getFrontmatter().mode).toBe("all");
expect(toolSubagent.getFrontmatter()).toMatchObject({
mode: "all",
temperature: 0.4,
});
});

it("should preserve primary mode for Kilo subagent", () => {
Expand Down Expand Up @@ -268,7 +269,7 @@ Assist with any tasks`,
expect(result.success).toBe(true);
});

it("should apply default mode 'subagent' when mode is omitted", async () => {
it("should apply default mode 'all' when mode is omitted", async () => {
const dirPath = join(testDir, ".kilo", "agent");
const filePath = join(dirPath, "no-mode.md");

Expand All @@ -285,7 +286,7 @@ Body content`,
relativeFilePath: "no-mode.md",
});

expect(subagent.getFrontmatter().mode).toBe("subagent");
expect(subagent.getFrontmatter().mode).toBe("all");
});

it("should preserve custom mode value when explicitly set", async () => {
Expand Down
37 changes: 16 additions & 21 deletions src/features/subagents/kilo-subagent.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { join } from "node:path";

import { z } from "zod/mini";

import { ToolTarget } from "../../types/tool-targets.js";
import { formatError } from "../../utils/error.js";
import { readFileContent } from "../../utils/file.js";
import { parseFrontmatter, stringifyFrontmatter } from "../../utils/frontmatter.js";
import {
OpenCodeStyleSubagent,
OpenCodeStyleSubagentFrontmatter,
OpenCodeStyleSubagentFrontmatterSchema,
OpenCodeStyleSubagentParams,
} from "./opencode-style-subagent.js";
import { OpenCodeStyleSubagent, OpenCodeStyleSubagentParams } from "./opencode-style-subagent.js";
import { RulesyncSubagent } from "./rulesync-subagent.js";
import {
ToolSubagent,
Expand All @@ -19,8 +16,14 @@ import {
ToolSubagentSettablePaths,
} from "./tool-subagent.js";

export const KiloSubagentFrontmatterSchema = OpenCodeStyleSubagentFrontmatterSchema;
export type KiloSubagentFrontmatter = OpenCodeStyleSubagentFrontmatter;
export const KiloSubagentFrontmatterSchema = z.looseObject({
description: z.optional(z.string()),
// Kilo's documented default for user-defined agents is "all":
// available both as a top-level pick and as a subagent.
mode: z._default(z.string(), "all"),
name: z.optional(z.string()),
});
export type KiloSubagentFrontmatter = z.infer<typeof KiloSubagentFrontmatterSchema>;
export type KiloSubagentParams = OpenCodeStyleSubagentParams;

export class KiloSubagent extends OpenCodeStyleSubagent {
Expand All @@ -47,21 +50,11 @@ export class KiloSubagent extends OpenCodeStyleSubagent {
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
const kiloSection = rulesyncFrontmatter.kilo ?? {};

const kiloFrontmatter: KiloSubagentFrontmatter = {
const kiloFrontmatter: KiloSubagentFrontmatter = KiloSubagentFrontmatterSchema.parse({
...kiloSection,
description: rulesyncFrontmatter.description,
// Kilo CLI's documented default for user-defined agents is "all"
// (available both as a top-level pick AND as a subagent). See
// https://kilocode.ai/docs/customize/custom-modes — "mode" reference:
// all — Available both as a top-level pick and as a subagent
// (default for user-defined agents).
// Previously this defaulted to "subagent", which hid generated
// agents from Kilo's agent picker. The explicit `kilo.mode` override
// in source frontmatter still wins, so users wanting subagent-only
// can opt in with `kilo: { mode: subagent }`.
mode: typeof kiloSection.mode === "string" ? kiloSection.mode : "all",
...(rulesyncFrontmatter.name && { name: rulesyncFrontmatter.name }),
};
});

const body = rulesyncSubagent.getBody();
const fileContent = stringifyFrontmatter(body, kiloFrontmatter);
Expand Down Expand Up @@ -118,15 +111,17 @@ export class KiloSubagent extends OpenCodeStyleSubagent {
outputRoot = process.cwd(),
relativeDirPath,
relativeFilePath,
global = false,
}: ToolSubagentForDeletionParams): KiloSubagent {
return new KiloSubagent({
outputRoot,
relativeDirPath,
relativeFilePath,
frontmatter: { description: "", mode: "subagent" },
frontmatter: { description: "", mode: "all" },
body: "",
fileContent: "",
validate: false,
global,
});
}
}
Loading