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
5 changes: 5 additions & 0 deletions .changeset/quiet-metadata-generators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kilocode/cli": patch
---

Keep Kilo's persona out of generated conversation titles and Agent Manager branch names.
4 changes: 4 additions & 0 deletions packages/opencode/src/kilocode/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import * as Log from "@opencode-ai/core/util/log"
const log = Log.create({ service: "kilocode.system-prompt" })

export namespace KilocodeSystemPrompt {
export function shouldIncludePersona(agent: string) {
return agent !== "title" && agent !== "branch-name"
}

export function environment(input: { ctx: InstanceContext; model: Provider.Model; editor?: EditorContext }) {
return [
[
Expand Down
12 changes: 7 additions & 5 deletions packages/opencode/src/session/llm/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { Identity } from "@kilocode/kilo-telemetry"
import { KiloSession } from "@/kilocode/session"
import { stripInternalOptions } from "@/kilocode/agent/options"
import { KilocodeSystemPrompt } from "@/kilocode/system-prompt"
// kilocode_change end

type PrepareInput = {
Expand Down Expand Up @@ -67,10 +68,11 @@ const mergeOptions = (target: Record<string, any>, source: Record<string, any> |

export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: PrepareInput) {
const isOpenaiOauth = input.provider.id === "openai" && input.auth?.type === "oauth"
const includePersona = KilocodeSystemPrompt.shouldIncludePersona(input.agent.name) // kilocode_change
const system = [
[
// kilocode_change start - soul defines core identity and personality
...(isOpenaiOauth ? [] : [SystemPrompt.soul()]),
...(isOpenaiOauth || !includePersona ? [] : [SystemPrompt.soul()]),
// kilocode_change end
...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)),
...input.system,
Expand Down Expand Up @@ -116,10 +118,10 @@ export const prepare = Effect.fn("LLMRequestPrep.prepare")(function* (input: Pre
delete options.include
}
if (isOpenaiOauth) {
// kilocode_change start - prepend soul to instructions
options.instructions = SystemPrompt.soul() + "\n" + system.join("\n")
// kilocode_change end
}
// kilocode_change start - prepend soul to instructions
options.instructions = [...(includePersona ? [SystemPrompt.soul()] : []), ...system].join("\n")
// kilocode_change end
}

const messages =
isOpenaiOauth || input.isWorkflow
Expand Down
127 changes: 127 additions & 0 deletions packages/opencode/test/kilocode/session-llm-request.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import type { ModelMessage } from "ai"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import type { Agent } from "@/agent/agent"
import type { Auth } from "@/auth"
import { RuntimeFlags } from "@/effect/runtime-flags"
import type { Plugin } from "@/plugin"
import type { Provider } from "@/provider/provider"
import { LLMRequestPrep } from "@/session/llm/request"
import { MessageID, SessionID } from "@/session/schema"
import { SystemPrompt } from "@/session/system"

const model: Provider.Model = {
id: ModelV2.ID.make("test-model"),
providerID: ProviderV2.ID.make("test"),
api: {
id: "test-model",
url: "https://example.com/v1",
npm: "@ai-sdk/openai",
},
name: "Test model",
capabilities: {
temperature: true,
reasoning: false,
attachment: false,
toolcall: true,
input: { text: true, audio: false, image: false, video: false, pdf: false },
output: { text: true, audio: false, image: false, video: false, pdf: false },
interleaved: false,
},
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
limit: { context: 128_000, output: 32_000 },
status: "active",
options: {},
headers: {},
release_date: "2026-01-01",
}

const plugin: Plugin.Interface = {
init: () => Effect.void,
trigger: (_name, _input, output) => Effect.succeed(output),
list: () => Effect.succeed([]),
}

function agent(name: string): Agent.Info {
return {
name,
mode: "primary",
options: {},
permission: [],
prompt: `${name} generation prompt`,
}
}

function user(name: string): SessionV1.User {
return {
id: MessageID.make("msg_test"),
sessionID: SessionID.make("ses_test"),
role: "user",
time: { created: Date.now() },
agent: name,
model: { providerID: model.providerID, modelID: model.id },
system: "request-specific system text",
}
}

async function prepare(name: string, oauth = false) {
const auth: Auth.Info | undefined = oauth
? { type: "oauth", refresh: "refresh", access: "access", expires: Date.now() + 60_000 }
: undefined
const provider: Provider.Info = {
id: ProviderV2.ID.make(oauth ? "openai" : "test"),
name: "Test provider",
source: "config",
env: [],
options: {},
models: {},
}
const flags = await Effect.runPromise(
RuntimeFlags.Service.pipe(Effect.provide(RuntimeFlags.layer({ client: "test" }))),
)
return Effect.runPromise(
LLMRequestPrep.prepare({
user: user(name),
sessionID: "ses_test",
model,
agent: agent(name),
system: [],
messages: [{ role: "user", content: "Generate a name" }] satisfies ModelMessage[],
tools: {},
provider,
auth,
plugin,
flags,
isWorkflow: false,
}),
)
}

describe("Kilo persona in generated metadata requests", () => {
test.each(["title", "branch-name"])("omits the persona for %s generation", async (name) => {
const result = await prepare(name)

expect(result.system[0]).toContain(`${name} generation prompt`)
expect(result.system[0]).toContain("request-specific system text")
expect(result.system[0]).not.toContain(SystemPrompt.soul())
})

test.each(["title", "branch-name"])("omits the persona from OpenAI OAuth %s generation", async (name) => {
const result = await prepare(name, true)

expect(result.params.options.instructions).toContain(`${name} generation prompt`)
expect(result.params.options.instructions).toContain("request-specific system text")
expect(result.params.options.instructions).not.toContain(SystemPrompt.soul())
})

test("keeps the persona for ordinary agent requests", async () => {
const result = await prepare("code")
const oauth = await prepare("code", true)

expect(result.system[0]).toContain(SystemPrompt.soul())
expect(oauth.params.options.instructions).toContain(SystemPrompt.soul())
})
})
Loading