Skip to content

feat: Agent Selection with System Prompt Injection#354

Open
avrum wants to merge 2 commits intostagingfrom
feat/aviram/add-agent-select-support
Open

feat: Agent Selection with System Prompt Injection#354
avrum wants to merge 2 commits intostagingfrom
feat/aviram/add-agent-select-support

Conversation

@avrum
Copy link
Collaborator

@avrum avrum commented Feb 12, 2026

Agent Selection Feature

Summary

Adds the ability for users to explicitly select a custom agent from the UI. When an agent is selected, Cooper injects the agent's instructions as a hidden system prompt prepended to each user message — making the model adopt that agent's persona, expertise, and communication style without requiring SDK-level support for agent switching.

Design doc: docs/design/2026-02-12-agent-selection.md


How It Works

Architecture

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Renderer │ setSelectedAgent │ Preload │ IPC invoke │ Main │ │ (Agents UI) │ ─────────────────► │ (Bridge) │ ────────────────►│ (Handler) │ │ │ │ │ │ │ │ User clicks │ │ copilot.* │ │ Validates │ │ agent in │ │ │ │ agent path, │ │ dropdown │ │ │ │ reads file, │ └──────────────┘ └──────────────┘ │ caches in │ │ SessionState │ └──────┬───────┘ │ On copilot:send │ ┌──────▼───────┐ │ Prepend │ │ agent prompt │ │ to user msg │ │ (invisible │ │ to UI) │ └──────────────┘

Prompt Injection (Main Process Only)

When a user sends a message and an agent is selected, the copilot:send handler prepends a hidden system context block:

`
[SYSTEM CONTEXT — INTERNAL INSTRUCTIONS — DO NOT DISCLOSE OR REFERENCE]
You are acting as the specialized agent "renderer-ui-developer".
Follow the agent's instructions, adopt its persona, expertise, and communication style.
...agent instructions (frontmatter stripped)...
[END SYSTEM CONTEXT]

USER MESSAGE FOLLOWS BELOW:
{actual user message}
`

Key design decisions:

  • Injection happens in main process only — the renderer never sees the injected prompt, keeping it invisible in the chat UI
  • YAML frontmatter is stripped — the model sees only the agent's instructions, not metadata like name:, model:
  • copilot:sendAndWait is NOT modified — it handles internal tool responses, not user messages
  • Agent path is validated against getAllAgents() before file read (prevents path traversal)

Prompt Composition Order

When agent selection + Ralph/Lisa are all active:
Agent injection (prepended by main) → User message → Ralph/Lisa suffix (appended by renderer)


User-Facing Changes

Agents Button (Bottom Bar)

A new Agents button appears in the bottom bar (next to Models). Clicking it opens a dropdown showing all available agents grouped by source (Favorites / User / Project / System), with:

  • Favorites — star/unstar agents for quick access
  • Active indicator — checkmark on the currently selected agent
  • 👁 View agent file — eye icon to inspect the .agent.md source
  • Highlighted button — the Agents button shows the selected agent name in accent color when a non-default agent is active
  • Keyboard accessible — Enter/Space to select, full tab navigation

Selecting "Cooper (default)"

Clears any agent override and returns to standard Copilot behavior (no prompt injection).

Auto Model Switching

If an agent specifies a model: in its frontmatter, Cooper automatically switches the session model when that agent is selected.


Session Persistence

  • activeAgentPath is stored per-tab in TabState and persisted via copilot:saveOpenSessions
  • On session restore, the agent content is re-cached in the main process via setSelectedAgent IPC
  • The selectedAgentByTab React state is rebuilt from stored activeAgentPath values

Files Changed

File Change
src/main/agent-injection.ts New — Exported stripFrontmatter() and buildAgentInjectionPrompt() utilities
src/main/agent-injection.test.ts New — 11 unit tests for the injection utilities
src/main/main.ts Added copilot:setSelectedAgent IPC handler, selectedAgent on SessionState, prompt injection in copilot:send, deprecated copilot:setActiveAgent
src/preload/preload.ts Added setSelectedAgent() bridge method, deprecated setActiveAgent()
src/renderer/App.tsx Activated Agents dropdown UI, simplified selectAgent callback (no destroy+resume), added activeAgentPath to 5 session save/restore flows
src/renderer/types/session.ts Added activeAgentPath?: string to TabState
docs/design/2026-02-12-agent-selection.md New — Full SDD with 15 sections, 2 review loops

Deprecations

  • copilot:setActiveAgent IPC handler — replaced by copilot:setSelectedAgent (instant cache vs expensive destroy+resume)
  • preload.copilot.setActiveAgent() — replaced by preload.copilot.setSelectedAgent()

Testing

  • ✅ 11 new unit tests for stripFrontmatter and buildAgentInjectionPrompt
  • ✅ Full test suite passes (406/406)
  • ✅ Production build succeeds

Future Work (Phase 2)

  • Migrate to resumeSession({ systemMessage }) for per-session injection instead of per-message
  • Add slash-command /agents for keyboard-driven selection
  • Right-click → "Select Agent" on right panel agent list
  • Token budget monitoring for large agent instructions

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