diff --git a/.changeset/host-identity-system-prompt.md b/.changeset/host-identity-system-prompt.md new file mode 100644 index 00000000000..aec1a9deffd --- /dev/null +++ b/.changeset/host-identity-system-prompt.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/agent-core-v2": patch +--- + +Let embedding hosts customize the agent's product name and reply-style guidance in the system prompt when starting the server. diff --git a/packages/agent-core-v2/docs/state-manifest.d.ts b/packages/agent-core-v2/docs/state-manifest.d.ts index 6c6e8ae80cf..90050ef3ba3 100644 --- a/packages/agent-core-v2/docs/state-manifest.d.ts +++ b/packages/agent-core-v2/docs/state-manifest.d.ts @@ -200,6 +200,8 @@ export interface SessionStateSnapshot { readonly now?: string; readonly skills?: string; readonly skillActive?: boolean; + readonly productName?: string; + readonly replyStyleGuide?: string; [key: string]: unknown; }) => string; readonly promptPrefix?: (ctx: /* AgentProfilePromptPrefixContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -264,6 +266,8 @@ export interface SessionStateSnapshot { readonly now?: string; readonly skills?: string; readonly skillActive?: boolean; + readonly productName?: string; + readonly replyStyleGuide?: string; [key: string]: unknown; }) => string; readonly promptPrefix?: (ctx: /* AgentProfilePromptPrefixContext — packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts */ { @@ -1006,7 +1010,7 @@ export interface AgentStateSnapshot { 'llmRequester.lastConfigLogSignature': string | undefined; 'llmRequester.mediaDegradedTurns': Set; 'llmRequester.mediaStrippedTurns': Map; 'llmRequester.turnConfigs': Map 0 ? `${shellName} (\`${shellPath}\`)` : '', diff --git a/packages/agent-core-v2/src/app/agentProfileCatalog/system.md b/packages/agent-core-v2/src/app/agentProfileCatalog/system.md index fe939693a87..452bc9128d4 100644 --- a/packages/agent-core-v2/src/app/agentProfileCatalog/system.md +++ b/packages/agent-core-v2/src/app/agentProfileCatalog/system.md @@ -1,4 +1,4 @@ -You are Kimi Code CLI, an interactive general AI agent running on a user's computer. +You are ${product_name}, an interactive general AI agent running on a user's computer. Your primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements. @@ -18,7 +18,7 @@ When handling the user's request, if it involves creating, modifying, or running When a dedicated tool fits the job, reach for it before raw shell: `Read` a known path, `Glob` to find files by name, and `Grep` to search file contents. These resolve paths through the workspace access policy and cap their output, so they keep large raw dumps out of the conversation. -Your text replies render as Markdown in the user's terminal. Use light Markdown that reads well there: short paragraphs, `-` bullets for lists, backticks for code, commands, paths, and identifiers, and fenced blocks for multi-line code. Keep structure shallow — avoid deep nesting, large tables, and heavy headings in ordinary replies. Do not use emoji unless the user does first or asks for it. Default to prose; reach for a list only when the content is genuinely a set of items or steps. When you point to a specific code location, cite it as `path/to/file.ts:42` — a precise, consistent reference the user can navigate to. +${reply_style_guide} You have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance. This applies especially to read-only investigation — issue independent `Read`, `Grep`, and `Glob` calls in parallel rather than one after another. diff --git a/packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts b/packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts new file mode 100644 index 00000000000..8720222a72c --- /dev/null +++ b/packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts @@ -0,0 +1,56 @@ +/** + * `hostIdentity` domain (L3) — runtime identity of the embedding host. + * + * Holds process-level overrides the host product (CLI, desktop, …) injects at + * the composition root: `productName` fills the `${product_name}` slot in the + * base system-prompt template, `replyStyleGuide` replaces the + * `${reply_style_guide}` block (the CLI default describes Markdown rendering + * in a terminal). Composition roots set them through {@link hostIdentitySeed}; + * the registered default carries no overrides, so the template renders its CLI + * defaults. Bound at App scope. + */ + +import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation'; +import { LifecycleScope, registerScopedService, ScopeActivation, type ScopeSeed } from '#/_base/di/scope'; + +export interface HostIdentityOverrides { + readonly productName?: string; + readonly replyStyleGuide?: string; +} + +export interface IHostIdentity { + readonly _serviceBrand: undefined; + readonly productName?: string; + readonly replyStyleGuide?: string; +} + +export const IHostIdentity: ServiceIdentifier = + createDecorator('hostIdentity'); + +export class HostIdentity implements IHostIdentity { + declare readonly _serviceBrand: undefined; + + constructor( + readonly productName?: string, + readonly replyStyleGuide?: string, + ) {} +} + +export function hostIdentitySeed(overrides: HostIdentityOverrides | undefined): ScopeSeed { + if (overrides === undefined) return []; + if (overrides.productName === undefined && overrides.replyStyleGuide === undefined) return []; + return [ + [ + IHostIdentity as ServiceIdentifier, + new HostIdentity(overrides.productName, overrides.replyStyleGuide), + ], + ]; +} + +registerScopedService( + LifecycleScope.App, + IHostIdentity, + HostIdentity, + ScopeActivation.OnScopeCreated, + 'hostIdentity', +); diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index c78cd2f213d..fd5732a86e7 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -174,6 +174,7 @@ export * from '#/app/agentFileCatalog/configSection'; export * from '#/app/agentFileCatalog/agentProfileSource'; export * from '#/app/agentFileCatalog/agentCatalogRuntimeOptions'; export * from '#/app/agentFileCatalog/userFileAgentSource'; +export * from '#/app/hostIdentity/hostIdentity'; export * from '#/app/plugin/types'; export * from '#/app/plugin/commands'; export * from '#/app/plugin/manifest'; diff --git a/packages/agent-core-v2/test/agent/profile/profileOps.test.ts b/packages/agent-core-v2/test/agent/profile/profileOps.test.ts index ae6a19847d5..13a4d6d44f2 100644 --- a/packages/agent-core-v2/test/agent/profile/profileOps.test.ts +++ b/packages/agent-core-v2/test/agent/profile/profileOps.test.ts @@ -20,6 +20,7 @@ import { IAgentStateService } from '#/agent/state/agentState'; import { AgentStateService } from '#/agent/state/agentStateService'; import { IHostEnvironment } from '#/os/interface/hostEnvironment'; import { IHostFileSystem } from '#/os/interface/hostFileSystem'; +import { IHostIdentity } from '#/app/hostIdentity/hostIdentity'; import { AppendLogStore } from '#/persistence/backends/node-fs/appendLogStore'; import { InMemoryStorageService } from '#/persistence/backends/memory/inMemoryStorageService'; import { IAppendLogStore } from '#/persistence/interface/appendLogStore'; @@ -210,6 +211,7 @@ function buildHost(key: string): { host.stub(IProtocolAdapterRegistry, createProtocolRegistryStub()); host.stub(IHostEnvironment, stubUnused()); host.stub(IHostFileSystem, stubUnused()); + host.stub(IHostIdentity, stubUnused()); host.stub(IBootstrapService, stubUnused()); host.stub(ISessionContext, createSessionContextStub()); host.stub(ISessionWorkspaceContext, stubUnused()); diff --git a/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts b/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts index a8bd9d6e635..b45780ccedd 100644 --- a/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts +++ b/packages/agent-core-v2/test/app/agentProfileCatalog/profile-shared.test.ts @@ -84,6 +84,23 @@ describe('systemPromptVars', () => { ).toContain('IMPORTANT: You are on Windows'); expect(systemPromptVars({ osKind: 'macOS' }, { skillActive: true })['windows_notes']).toBe(''); }); + + it('defaults host-identity variables to the CLI text', () => { + const vars = systemPromptVars({}, { skillActive: true }); + + expect(vars['product_name']).toBe('Kimi Code CLI'); + expect(vars['reply_style_guide']).toContain("render as Markdown in the user's terminal"); + }); + + it('lets the context override host-identity variables', () => { + const vars = systemPromptVars( + { productName: 'Kimi Desktop', replyStyleGuide: 'GUI_STYLE' }, + { skillActive: true }, + ); + + expect(vars['product_name']).toBe('Kimi Desktop'); + expect(vars['reply_style_guide']).toBe('GUI_STYLE'); + }); }); describe('renderPromptTemplate', () => { @@ -183,4 +200,19 @@ describe('renderSystemPrompt', () => { expect(prompt).not.toMatch(/\$\{[A-Za-z_][A-Za-z0-9_]*\}/); }); + + it('renders the host identity from the context, defaulting to the CLI text', () => { + const fallback = renderSystemPrompt('', {}, { skillActive: true }); + expect(fallback).toContain('You are Kimi Code CLI,'); + expect(fallback).toContain("render as Markdown in the user's terminal"); + + const overridden = renderSystemPrompt( + '', + { productName: 'Kimi Desktop', replyStyleGuide: 'GUI_STYLE' }, + { skillActive: true }, + ); + expect(overridden).toContain('You are Kimi Desktop,'); + expect(overridden).toContain('GUI_STYLE'); + expect(overridden).not.toContain('Kimi Code CLI'); + }); }); diff --git a/packages/kap-server/src/start.ts b/packages/kap-server/src/start.ts index fb6c37fad96..6fe6b2626d1 100644 --- a/packages/kap-server/src/start.ts +++ b/packages/kap-server/src/start.ts @@ -9,6 +9,7 @@ import { bootstrap, + hostIdentitySeed, hostRequestHeadersSeed, IConfigService, IProviderDiscoveryService, @@ -18,6 +19,7 @@ import { resolveKimiHome, resolveLoggingConfig, skillCatalogRuntimeOptionsSeed, + type HostIdentityOverrides, type Scope, type ScopeSeed, } from '@moonshot-ai/agent-core-v2'; @@ -104,6 +106,14 @@ export interface ServerStartOptions { readonly rpcToken?: string; /** Extra scope seeds applied at bootstrap (e.g. a host-provided `ISessionModelResolver`). */ readonly seeds?: ScopeSeed; + /** + * Host product identity injected into the base system prompt: `productName` + * fills the `${product_name}` slot, `replyStyleGuide` replaces the + * `${reply_style_guide}` block. Applied to every agent the server hosts — for + * embedding hosts (e.g. a desktop app), not per-session use. Defaults render + * the CLI text. + */ + readonly hostIdentity?: HostIdentityOverrides; /** * Explicit skill directories for this process (v1's SDK `skillDirs`): when * non-empty, default user / project skill discovery is skipped and these @@ -210,6 +220,7 @@ export async function startServer(opts: ServerStartOptions = {}): Promise