Skip to content

Feature Request: Enable dynamic agents with live system prompt updates via session.systemMessage.set() #2790

Description

@jgbradley1

Overview

I'm interested in running dynamic agents that can evolve over time, where the base system prompt can change. Currently the SDK only supports defining the system prompt as a static agent (see workaround below).

Add the ability for users to update a session's base system message (the default top-level agent's system prompt) while a session is active, without stopping and resuming. The update would apply on the next user turn, queue if a turn is in flight, and preserve the full conversation history.

Problem / Motivation

Agent behavior evolves within a session, but today the system prompt is fixed once the session starts. The only way to change the agent's core instructions is to stop and resume — a heavyweight operation that disrupts a healthy, in-progress session. This forces developers to either cram every phase into one monolithic prompt or tear down sessions just to adjust behavior.

Live system prompt updates would make agents genuinely steerable: applications can adapt an agent's persona, constraints, and priorities on the fly while preserving the full conversation history, unlocking phase-aware workflows, dynamic policy enforcement, and adaptive behavior. By reusing the existing SystemMessageConfig and applying cleanly on the next turn, it delivers this with minimal new surface area and a strong correctness guarantee — a small, well-bounded change with broad impact.

Today the base system message is configurable only at session creation or resume via SystemMessageConfig (createSession / resumeSession). There is no way to update it on a running session:

  • The live mutable-options RPC session.options.update (SessionUpdateOptionsParams) exposes many configurable options — model, tools, working directory, skills, instruction sources, etc. — but has no systemMessage field.
  • The systemMessage.transform callback the runtime uses to materialize customized sections fires only during session creation/resume.

Temporary workaround:

Changing the agent's actual system prompt currently requires a full stop + resume. This is heavyweight (re-initialization, lifecycle event replay) for use cases that need to steer the agent's behavior mid-session — e.g. switching persona/policy after a phase transition, injecting updated org/policy instructions, or adapting tone/guidelines in response to runtime conditions — while keeping the existing conversation intact.

Proposal

Add a dedicated session-scoped JSON-RPC method, modeled after the existing session.agent.setPrompt:

  • Method: session.systemMessage.set
  • Params (SessionSystemMessageSetParams): { sessionId: string; systemMessage: SystemMessageConfig } — reuse the same config shape already accepted by create/resume (plain content, or mode: "customize" with per-section replace / append / remove / prepend / preserve / transform actions).
  • Result (SessionSystemMessageSetResult): { success: boolean; status?: "applied" | "queued" }.

Runtime semantics:

  1. Store the new config on the session.
  2. If a turn is active, queue and apply after it completes (status: "queued"); otherwise apply immediately (status: "applied").
  3. Rebuild the system prompt from the stored config on the next turn.
  4. For transform sections, re-invoke the existing systemMessage.transform callback into the SDK during that rebuild.
  5. Never mutate the stored conversation event log.

SDK surface (all six languages), mirroring the setAutoTier wrapper pattern:

SDK Method
Node.js session.updateSystemMessage(config)
Python session.update_system_message(config)
Go session.UpdateSystemMessage(ctx, config)
.NET session.UpdateSystemMessageAsync(config)
Rust session.update_system_message(config)
Java session.updateSystemMessage(config)

Each wrapper would reuse the existing extractTransformCallbacks logic to split dynamic transform sections from the static wire payload, send the payload via the generated binding, and — only on success — swaps the session's live transform-callback map so the runtime's next systemMessage.transform invocation dispatches to new callbacks.

Why This Design

Prompt assembly is owned by the runtime, which rebuilds the system prompt fresh after each turn from two independent components: the system prompt and the separately-stored conversation history. A dedicated RPC that mutates only the stored SystemMessageConfig gives us a clean guarantee: the next turn will see the new system prompt plus the existing history, unchanged.

Context Preservation Guarantee

session.systemMessage.set would mutate only the system-prompt component; it never touches stored conversation events. Nuances:

  • Queued timing: an in-flight turn finishes under the old prompt; the next turn uses the new prompt with all history, including that just-completed turn.
  • History is not rewritten: prior turns remain in history verbatim, generated under the old prompt. The new prompt governs generation from the next turn forward.

Implementation Notes

A full solution requires a change in two places:

  1. Runtime (github/copilot-cli): add the session.systemMessage.set method + schema, implement the store/queue/rebuild/re-transform behavior, and ship it in the next CLI release.
  2. SDK (this repo): regenerate the RPC bindings, and add the thin handwritten per-language wrappers plus the transform-callback refresh.

This feature in the SDK is blocked until a solution in the runtime method ships, because the SDK wrappers call the generated typed facade (rpc.systemMessage.set), which only exists once the schema includes the method.

Acceptance Criteria

  • session.systemMessage.set exists in the runtime schema and is generated into all six SDKs.
  • Each SDK exposes the public wrapper listed above.
  • Calling it with mode: "customize" (including transform sections) updates the prompt on the next turn and re-invokes systemMessage.transform with the new callbacks.
  • Result reports queued when a turn is active, applied otherwise.
  • Conversation history is preserved and reused on the next turn (E2E replay coverage).
  • A failed send does not swap the SDK's transform-callback map.

I can be contacted at joshbradley@microsoft.com

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions