Skip to content

feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field - #2314

Open
7Sageer wants to merge 15 commits into
mainfrom
feat/plugin-system-prompt
Open

feat(agent-core-v2): let plugins contribute system prompt instructions via the manifest systemPrompt field#2314
7Sageer wants to merge 15 commits into
mainfrom
feat/plugin-system-prompt

Conversation

@7Sageer

@7Sageer 7Sageer commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

Plugins currently have no native way to contribute instructions to the agent's system prompt. Every existing manifest capability lands in the conversation context instead: sessionStart becomes a user-role <system-reminder> injection, skillInstructions only rides along when one of the plugin's skills is activated, and hooks append user/assistant messages. The only manifest field that reaches the system prompt at all is skills, and only as name/description entries in the skills listing. A plugin that wants to declare stable, always-on guidance (domain rules that should hold for the whole session) has no channel for it.

What changed

Adds systemPrompt and systemPromptPath fields to the plugin manifest (kimi.plugin.json): systemPrompt takes inline instruction text, while systemPromptPath loads it from a file inside the plugin (both may be set; inline content comes first). Content from enabled plugins is rendered into a new # Plugin Instructions section at the tail of the system prompt, with per-plugin provenance annotations and the same precedence disclaimer used for AGENTS.md content.

The implementation mirrors the existing ${skills_section} pipeline end to end:

  • Manifest parsing and the klient wire contract gain the optional systemPrompt string; systemPromptPath is resolved to text at manifest parse time, so the wire contract stays unchanged.
  • IPluginService.enabledSystemPrompts() exposes enabled plugins' declarations on the consumption plane (degrades to empty on plugin-state load failure, like the other consumption reads).
  • AgentProfileService assembles the sections into SystemPromptContext.pluginSections and participates in an awaitable plugin-change barrier to refresh the prompt.
  • systemPromptVars composes the conditional plugin_sections block; system.md places ${plugin_sections} after ${skills_section}, so agent files and SYSTEM.md templates get the variable too.

Lifecycle consistency

Successful plugin mutations — install, enable/disable, remove, and explicit reload — publish an awaitable onDidChange event after the catalog state is committed, tagged with a change kind (catalog or mcp). A Session-level convergence point first reloads plugin skills into the session skill catalog, then fans out to every live agent, which re-renders its prompt from the converged catalog and the current plugin sections; the mutation call returns only after the whole pipeline finishes, so prompts and skill listings never straddle a mutation. MCP-server toggles carry the mcp kind and skip prompt convergence entirely.

A restored agent keeps its replayed profile binding — prompt and tool set — exactly as persisted; restore never re-renders. A fork pins its source agent's profile object at fork time, so only a genuinely cold binding (a restore, or a fork of one) ever re-resolves the profile from the catalog. An agent created while a plugin convergence is in flight waits for it (a restored one refreshes once after it), so a mutation never straddles an agent's bootstrap. Live refreshes re-render from the pinned in-memory profile; after a restart, the first refresh re-resolves the profile by name and rebinds the full slice (prompt, disallowed tools, active tools) atomically, warning and keeping the persisted state when the profile no longer exists. Renders reuse the agent's first-render timestamp of the current process and unchanged prompts are not re-persisted, so steady-state convergence never appends replay-visible config records within a process lifetime. Contributions are capped at 32 KB per plugin field or file (oversized content is ignored with a diagnostic) and 64 KB in aggregate per prompt build (excess skipped with a warning).

Requests already in flight are not modified retroactively.

Scope note: this is implemented on the agent-core-v2 engine (which backs kimi web and the server). The legacy v1 engine used by the default TUI ignores the field; porting it is a separate follow-up, and the user docs are intentionally left for that port.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1262bd1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 28, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@1262bd1
npx https://pkg.pr.new/@moonshot-ai/kimi-code@1262bd1

commit: 1262bd1

7Sageer added 14 commits July 28, 2026 18:51
…gin contributions at session scope

- restore no longer re-renders or re-persists prompts: a resumed agent
  keeps its replayed profile binding (prompt and tool set) as persisted
- a new Session-level convergence point reloads plugin skills into the
  session skill catalog before fanning out to every live agent prompt,
  and every catalog-kind plugin mutation awaits the whole pipeline;
  MCP-only toggles carry a distinct change kind and skip it
- live refreshes after a restart re-resolve the bound profile by name
  and rebind the full slice (prompt, disallowed tools, active tools)
  atomically, warning and keeping the persisted state when the profile
  is gone; renders reuse the first-render timestamp and unchanged
  prompts are not re-persisted, so convergence never churns the wire
- cap plugin system-prompt contributions (32 KB per field/file, 64 KB
  aggregate per prompt build) with manifest diagnostics and warnings
- bump the changeset to minor: this is a new user-facing capability
…ssing-profile warning

- add sessionPluginContribution to the domain-layer registry so
  lint:domain stays green
- emit system-prompt-refresh-profile-missing once per profile name,
  matching the service's other deduped warnings
- document the convergence timeout escape hatch and the klient
  exclusion of enabledSystemPrompts
…ion read failures

- emit plugin-sections-oversized once per skipped-plugin signature
- let enabledSystemPrompts failures propagate to the refresh catch
  (keeps the current prompt and warns) instead of silently rendering
  and persisting a prompt without plugin instructions
- cover the convergence timeout cut-off with a fake-timers test
- clarify that the first-render timestamp anchors per process
…load timing

- run at most one convergence per session and bound each change's wait
  by the timeout, so a fan-out emitter never interleaves deliveries
  after a timed-out convergence
- fire onDidReload as soon as the reload commits again, keeping hook
  reloads independent of prompt convergence
- sign the plugin budget warning with an unambiguous key
…mantics

- the timeout retry promise only holds once stalled work clears
- note the per-session serial delivery cost model on the plugin change
  contract and the dual-queue invariant on the service
…ompt template

- place ${plugin_sections} on the same template line as
  ${skills_section} so prompts without either block render exactly as
  before this feature
- note on the change contract that waitUntil work must not call back
  into plugin mutations, and spell out the per-session convergence
  order in the user docs
…bind it

- applyBindingSnapshot left the fork with no pinned profile, which
  routed in-process forks into the post-restart catalog rebind and
  could reset an inherited tool set; forks now inherit the source
  agent's pinned profile object
- pin the first-render timestamp reuse with a ${now}-embedding test
  and document the anchored ${now} semantics
- tighten the plugin docs budget and resume-refresh wording
- an agent created while a plugin convergence is in flight now waits
  for it, and a restored agent refreshes once after it, so a plugin
  mutation never straddles an agent's bootstrap
- warn on a non-string systemPrompt field and strip a UTF-8 BOM from
  systemPromptPath files before trimming
- correct the consumption-surface wording (every CLI surface on the
  experimental flag, not just kimi -p), the per-session queueing note,
  and the single-plugin combined budget clause
A permanently wedged convergence kept convergeTail pending forever,
and the unconditional settled() wait in bindBootstrap would have
blocked every later agent creation in that session; the join now
races the shared convergence timeout and continues (a restored agent
still refreshes once, which never touches the tail), and the timeout
constant moves to the contract for reuse
…stores

- a convergence fan-out could land while an agent's wire log is still
  replaying, dispatching a replay-visible config record whose effect
  the rest of the replay then overwrites; refreshSystemPrompt now
  skips while the wire restore is in progress
- convergence completion is tracked by a generation counter; bootstrap
  compares it (after a bounded join) and refreshes a restored agent
  exactly once when a round completed after its creation began,
  replacing the wasConverging flag that could miss both windows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant