From 786eca5a02a071a706697bc3b8898362a1409598 Mon Sep 17 00:00:00 2001 From: "haozhe.yang" Date: Fri, 24 Jul 2026 16:37:36 +0800 Subject: [PATCH 1/3] feat(agent-core-v2): add hostIdentity domain for host-overridable system prompt identity - add app-scope hostIdentity domain (L3) with productName / replyStyleGuide overrides and hostIdentitySeed for composition roots - render ${product_name} and ${reply_style_guide} in the base system prompt, falling back to CLI defaults when the host provides no override - seed the variables from AgentProfileService via IHostIdentity - expose hostIdentity on kap-server's ServerStartOptions --- .../scripts/check-domain-layers.mjs | 1 + .../src/agent/profile/profileService.ts | 4 ++ .../agentProfileCatalog.ts | 2 + .../app/agentProfileCatalog/profile-shared.ts | 13 ++++- .../src/app/agentProfileCatalog/system.md | 4 +- .../src/app/hostIdentity/hostIdentity.ts | 57 +++++++++++++++++++ packages/agent-core-v2/src/index.ts | 1 + .../test/agent/profile/profileOps.test.ts | 2 + .../profile-shared.test.ts | 32 +++++++++++ packages/kap-server/src/start.ts | 11 ++++ 10 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts diff --git a/packages/agent-core-v2/scripts/check-domain-layers.mjs b/packages/agent-core-v2/scripts/check-domain-layers.mjs index 58073156877..cca24df4325 100644 --- a/packages/agent-core-v2/scripts/check-domain-layers.mjs +++ b/packages/agent-core-v2/scripts/check-domain-layers.mjs @@ -159,6 +159,7 @@ const DOMAIN_LAYER = new Map([ ['modelCatalog', 3], ['agentProfileCatalog', 3], ['agentFileCatalog', 3], + ['hostIdentity', 3], // L4 — agent behaviour // `activityView` is the Agent-scope read model folding the agent's own event // bus into the activity projection (`agent.activity.updated`); it owns no diff --git a/packages/agent-core-v2/src/agent/profile/profileService.ts b/packages/agent-core-v2/src/agent/profile/profileService.ts index f2794924a6b..9c282609b54 100644 --- a/packages/agent-core-v2/src/agent/profile/profileService.ts +++ b/packages/agent-core-v2/src/agent/profile/profileService.ts @@ -93,6 +93,7 @@ import { IAgentTelemetryContextService } from '#/app/telemetry/agentTelemetryCon import { IWireService } from '#/wire/wire'; import type { PayloadOf } from '#/wire/types'; import { IEventBus } from '#/app/event/eventBus'; +import { IHostIdentity } from '#/app/hostIdentity/hostIdentity'; import { prepareSystemPromptContext } from './context'; import type { ApplyProfileOptions, @@ -197,6 +198,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ @IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService, @IAgentProfileCatalogService private readonly builtinProfiles: IAgentProfileCatalogService, @IAgentStateService private readonly states: IAgentStateService, + @IHostIdentity private readonly hostIdentity: IHostIdentity, ) { super(); this.states.register(profileActiveToolNamesOverlayKey); @@ -848,6 +850,8 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ now: new Date().toISOString(), skills, skillActive: this.isToolActiveForProfile(profile, 'Skill'), + productName: this.hostIdentity.productName, + replyStyleGuide: this.hostIdentity.replyStyleGuide, }; } diff --git a/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts b/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts index c7b80a92006..bd88a8bd3e4 100644 --- a/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts +++ b/packages/agent-core-v2/src/app/agentProfileCatalog/agentProfileCatalog.ts @@ -63,6 +63,8 @@ export interface AgentProfileContext { readonly now?: string; readonly skills?: string; readonly skillActive?: boolean; + readonly productName?: string; + readonly replyStyleGuide?: string; readonly [key: string]: unknown; } diff --git a/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts b/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts index 5ea47d71511..77a4622a848 100644 --- a/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts +++ b/packages/agent-core-v2/src/app/agentProfileCatalog/profile-shared.ts @@ -13,7 +13,11 @@ * context fields render as empty strings when missing and the composed * `*_section` / `windows_notes` blocks are empty unless their content exists, * so templates can place them on their own line without leaving stray - * headings behind. `renderPromptTemplate` renders a user-owned template (an + * headings behind. Host-identity blocks (`product_name`, `reply_style_guide`) + * work the same way: the context may carry overrides seeded by the embedding + * host (e.g. a desktop app), and the table falls back to the CLI defaults + * ({@link DEFAULT_PRODUCT_NAME}, {@link DEFAULT_REPLY_STYLE_GUIDE}) when it + * does not. `renderPromptTemplate` renders a user-owned template (an * agent-file body or `SYSTEM.md`) against the table; `${base_prompt}` is * bound to the default profile's prompt when a `basePrompt` is given, * resolved lazily and only when the template actually references it. Also @@ -61,6 +65,11 @@ export function subagentTypeNotAllowedMessage( const WINDOWS_NOTES = 'IMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.'; +export const DEFAULT_PRODUCT_NAME = 'Kimi Code CLI'; + +export const DEFAULT_REPLY_STYLE_GUIDE = + "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."; + const ADDITIONAL_DIRS_SECTION_PROSE = 'The following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.'; @@ -81,6 +90,8 @@ export function systemPromptVars( const additionalDirsInfo = context.additionalDirsInfo ?? ''; return { role_additional: '', + product_name: context.productName ?? DEFAULT_PRODUCT_NAME, + reply_style_guide: context.replyStyleGuide ?? DEFAULT_REPLY_STYLE_GUIDE, os: context.osKind ?? '', windows_notes: context.osKind === 'Windows' ? `\n\n${WINDOWS_NOTES}\n\n` : '', shell: shellName.length > 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..0897a5c1c8c --- /dev/null +++ b/packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts @@ -0,0 +1,57 @@ +/** + * `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 { InstantiationType } from '#/_base/di/extensions'; +import { LifecycleScope, registerScopedService, 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, + InstantiationType.Eager, + 'hostIdentity', +); diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 023fe5aba8f..7f441926bd4 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 Date: Fri, 24 Jul 2026 16:38:24 +0800 Subject: [PATCH 2/3] chore: add changeset for host identity system prompt --- .changeset/host-identity-system-prompt.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/host-identity-system-prompt.md 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. From b554495ecee1400e1331df8cc2d9c674597b4546 Mon Sep 17 00:00:00 2001 From: "haozhe.yang" Date: Mon, 27 Jul 2026 10:08:13 +0800 Subject: [PATCH 3/3] fix(agent-core-v2): adapt hostIdentity registration to ScopeActivation The DI refactor on main removed _base/di/extensions (InstantiationType); register with ScopeActivation.OnScopeCreated instead, and regenerate the state manifest for the new SystemPromptContext fields. --- packages/agent-core-v2/docs/state-manifest.d.ts | 6 +++++- packages/agent-core-v2/src/app/hostIdentity/hostIdentity.ts | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/agent-core-v2/docs/state-manifest.d.ts b/packages/agent-core-v2/docs/state-manifest.d.ts index e26afd44924..999e1069b91 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 */ { @@ -1002,7 +1006,7 @@ export interface AgentStateSnapshot { 'llmRequester.lastConfigLogSignature': string | undefined; 'llmRequester.mediaDegradedTurns': Set; 'llmRequester.mediaStrippedTurns': Map; 'llmRequester.turnConfigs': Map