Skip to content

feat: add personality traits system with per-mode configuration#11979

Open
jthweny wants to merge 1 commit intoRooCodeInc:mainfrom
jthweny:feat/personality-traits
Open

feat: add personality traits system with per-mode configuration#11979
jthweny wants to merge 1 commit intoRooCodeInc:mainfrom
jthweny:feat/personality-traits

Conversation

@jthweny
Copy link
Copy Markdown

@jthweny jthweny commented Mar 22, 2026

  • 13 built-in personality traits (dry-wit, professor, straight-shooter, etc.)
  • Custom trait creation with emoji picker and prompt editor
  • Per-mode personality configuration via PersonalityConfig on ModeConfig
  • LLM-powered trait enhancement for expanding brief descriptions
  • Sandwich technique: personality prompt injected at top and bottom of system prompt
  • PersonalityTraitsPanel component with pill-grid UI, active order badges
  • Full test coverage for trait resolution, merging, and prompt building

Made-with: Cursor

Related GitHub Issue

Closes: #

Roo Code Task Context (Optional)

Description

Test Procedure

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Screenshots / Videos

Documentation Updates

Additional Notes

Get in Touch

Interactively review PR in Roo Code Cloud

- 13 built-in personality traits (dry-wit, professor, straight-shooter, etc.)
- Custom trait creation with emoji picker and prompt editor
- Per-mode personality configuration via PersonalityConfig on ModeConfig
- LLM-powered trait enhancement for expanding brief descriptions
- Sandwich technique: personality prompt injected at top and bottom of system prompt
- PersonalityTraitsPanel component with pill-grid UI, active order badges
- Full test coverage for trait resolution, merging, and prompt building

Made-with: Cursor
Copilot AI review requested due to automatic review settings March 22, 2026 16:56
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. Enhancement New feature or request labels Mar 22, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a personality traits system to Roo Code, including UI for selecting/creating traits, persistence via mode config/types, and prompt-building helpers intended to inject personality into the system prompt.

Changes:

  • Introduces built-in traits + helpers for resolving/merging traits and building “sandwich” prompt parts.
  • Adds a new Modes UI panel to manage traits (toggle, preview, create/edit/delete) and an emoji picker.
  • Extends types/settings schemas to support per-mode personalityConfig and a global enhancer meta-prompt.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
webview-ui/src/i18n/locales/en/personality.json Adds EN strings for the personality traits UI.
webview-ui/src/components/modes/PersonalityTraitsPanel.tsx New UI panel for trait selection, preview, and custom trait CRUD.
webview-ui/src/components/modes/ModesView.tsx Mounts the new personality panel into the modes view.
webview-ui/src/components/modes/EmojiPicker.tsx New popover emoji picker used in trait editor.
src/shared/personality-traits.ts Adds built-in traits and prompt-building/trait-resolution utilities.
src/core/prompts/sections/personality.ts Exposes personality prompt section entrypoints for system prompt building.
src/core/prompts/sections/index.ts Exports personality section utilities.
src/core/prompts/sections/custom-instructions.ts Adds optional “personalityPrompt” injection at end of custom instructions.
src/core/prompts/sections/tests/personality.spec.ts Adds tests for trait resolution and prompt building.
packages/types/src/mode.ts Adds PersonalityTrait, PersonalityConfig, and ModeConfig.personalityConfig.
packages/types/src/global-settings.ts Adds personalityTraitEnhancerPrompt to global settings schema.
path/to/data/raft_state.json Adds a data file (appears to be runtime state).
path/to/data/aliases/data.json Adds a data file (appears to be runtime state).
Untitled-1.jsonc Adds a large VS Code theme JSONC file.
.vscode/settings.json Adds ndjson.port to workspace settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +23 to +35
it("should return formatted section for a single active built-in trait", () => {
const config: PersonalityConfig = {
activeTraitIds: ["roo"],
customTraits: [],
}

const result = buildPersonalityPrompt(config)

expect(result).toContain("Personality & Communication Style:")
expect(result).toContain("non-negotiable")
expect(result).toContain("You are Roo")
expect(result).toContain("IMPORTANT: Maintaining this personality is critical")
})
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test suite’s expectations don’t match the implementation in shared/personality-traits.ts. For example, buildPersonalityPrompt now emits PERSONALITY & VOICE (ACTIVE: ...) and CRITICAL: wording, so assertions like Personality & Communication Style: / non-negotiable / the anchor text will fail. Update the expected strings to reflect the new prompt format (or adjust the implementation to match the intended spec).

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +125
describe("Built-in traits", () => {
it("should have 12 built-in traits", () => {
expect(BUILT_IN_PERSONALITY_TRAITS).toHaveLength(12)
})

it("should have unique IDs", () => {
const ids = BUILT_IN_PERSONALITY_TRAITS.map((t) => t.id)
expect(new Set(ids).size).toBe(ids.length)
})

it("should all be marked as isBuiltIn", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
expect(trait.isBuiltIn).toBe(true)
})
})

it("should all use direct natural-language format (no section markers)", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
// No [SECTION_KEY] markers should be present
expect(trait.prompt).not.toMatch(/\[COMMUNICATION_STYLE\]/)
expect(trait.prompt).not.toMatch(/\[TASK_COMPLETION\]/)
expect(trait.prompt).not.toMatch(/\[ERROR_HANDLING\]/)
expect(trait.prompt).not.toMatch(/\[SUGGESTIONS\]/)
})
})

it("should all start with identity-first framing (You are/You have/You speak/You prioritize/You question)", () => {
BUILT_IN_PERSONALITY_TRAITS.forEach((trait) => {
const startsWithIdentity = /^You (are|have|speak|prioritize|question|see)\b/.test(trait.prompt.trim())
expect(startsWithIdentity).toBe(true)
})
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The built-in traits assertions are inconsistent with BUILT_IN_PERSONALITY_TRAITS in shared/personality-traits.ts: the list currently contains 13 entries (including roo-devs), but the test expects 12. Additionally, the “identity-first framing” regex only allows You are|have|speak|prioritize|question|see, but multiple prompts start with You deliver / You talk etc. Align these tests with the actual built-in trait definitions (or standardize the prompts to satisfy the test constraints).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
{
"$schema": "vscode://schemas/color-theme",
"type": "light",
"colors": {
"activityBarBadge.background": "#007acc",
"editor.background": "#ffffff",
"editor.foreground": "#000000",
"editor.inactiveSelectionBackground": "#e5ebf1",
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untitled-1.jsonc is a large VS Code color theme dump and doesn’t appear related to the personality traits feature. If this was added accidentally, remove it from the PR (and consider adding editor-export artifacts to .gitignore if they’re commonly generated).

Copilot uses AI. Check for mistakes.
Comment on lines +115 to +125
const generateTraitId = useCallback(
(label: string): string => {
const baseId = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")
let id = baseId
let attempt = 0
while (allTraits.some((t) => t.id === id)) {
attempt++
id = `${baseId}-${attempt}`
}
return id
},
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateTraitId() can return an empty string when the label has no [a-z0-9] characters (e.g. "!!!" or non‑Latin text), which would violate the PersonalityTrait schema (id min length 1) and can create duplicate/invalid traits. Add a fallback baseId (e.g. "trait") when slugification results in an empty string, and consider trimming/collapsing consecutive dashes before uniqueness checks.

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +308
<button
onClick={(e) => {
e.stopPropagation()
startEditing(trait)
}}
className="w-5 h-5 rounded-full bg-vscode-badge-background text-vscode-badge-foreground flex items-center justify-center hover:bg-vscode-button-hoverBackground transition-colors">
<Pencil className="w-3 h-3" />
</button>
</StandardTooltip>
<StandardTooltip content={t("personality:deleteTrait")}>
<button
onClick={(e) => {
e.stopPropagation()
handleDeleteTrait(trait.id)
}}
className="w-5 h-5 rounded-full bg-vscode-badge-background text-vscode-badge-foreground flex items-center justify-center hover:bg-vscode-errorForeground transition-colors">
<Trash2 className="w-3 h-3" />
</button>
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The icon-only edit/delete buttons rely on tooltip/visual affordances but don’t set aria-label attributes. Add explicit aria-labels so keyboard/screen reader users can discover and use these actions reliably.

Copilot uses AI. Check for mistakes.
Comment on lines +495 to +501
// Inject personality prompt LAST for maximum recency effect.
// This is the last thing the model reads before generating,
// which research shows produces the strongest behavioral adherence.
if (options.personalityPrompt && options.personalityPrompt.trim()) {
sections.push(options.personalityPrompt.trim())
}

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new personalityPrompt option is never passed by any call sites (no matches for personalityPrompt: in src/), so the “inject personality prompt last” behavior won’t run in production. Ensure the system prompt generation path builds the personality bottom block and passes it into addCustomInstructions (or otherwise injects it).

Suggested change
// Inject personality prompt LAST for maximum recency effect.
// This is the last thing the model reads before generating,
// which research shows produces the strongest behavioral adherence.
if (options.personalityPrompt && options.personalityPrompt.trim()) {
sections.push(options.personalityPrompt.trim())
}

Copilot uses AI. Check for mistakes.
Comment on lines +206 to +211
const isBuiltIn = BUILT_IN_PERSONALITY_TRAITS.some((t) => t.id === traitId)
let newConfig = { ...personalityConfig }

if (isBuiltIn) {
// Mark built-in as deleted (can be restored later)
newConfig.deletedBuiltInTraitIds = [...(newConfig.deletedBuiltInTraitIds || []), traitId]
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When deleting a built-in trait, deletedBuiltInTraitIds appends the id without checking for duplicates. Repeated deletes will accumulate duplicates and make the config noisy. Consider guarding with includes() (or using a Set-like update) and also defensively prevent deleting the protected "roo" trait at this handler level.

Suggested change
const isBuiltIn = BUILT_IN_PERSONALITY_TRAITS.some((t) => t.id === traitId)
let newConfig = { ...personalityConfig }
if (isBuiltIn) {
// Mark built-in as deleted (can be restored later)
newConfig.deletedBuiltInTraitIds = [...(newConfig.deletedBuiltInTraitIds || []), traitId]
// Prevent deletion of protected built-in trait
if (traitId === "roo") {
return
}
const isBuiltIn = BUILT_IN_PERSONALITY_TRAITS.some((t) => t.id === traitId)
let newConfig = { ...personalityConfig }
if (isBuiltIn) {
// Mark built-in as deleted (can be restored later), avoiding duplicates
const existingDeleted = newConfig.deletedBuiltInTraitIds || []
if (!existingDeleted.includes(traitId)) {
newConfig.deletedBuiltInTraitIds = [...existingDeleted, traitId]
}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
{"state":{"hard_state":{"term":0,"vote":0,"commit":0},"conf_state":{"voters":[4166060179281456],"learners":[],"voters_outgoing":[],"learners_next":[],"auto_leave":false}},"latest_snapshot_meta":{"term":0,"index":0},"apply_progress_queue":null,"first_voter":4166060179281456,"peer_address_by_id":{},"peer_metadata_by_id":{},"this_peer_id":4166060179281456} No newline at end of file
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like generated runtime state (Raft hard_state/conf_state) rather than source code. Committing it will create noisy diffs and potentially leak environment-specific IDs. Remove it from the PR and add the containing path to .gitignore if it’s produced locally.

Suggested change
{"state":{"hard_state":{"term":0,"vote":0,"commit":0},"conf_state":{"voters":[4166060179281456],"learners":[],"voters_outgoing":[],"learners_next":[],"auto_leave":false}},"latest_snapshot_meta":{"term":0,"index":0},"apply_progress_queue":null,"first_voter":4166060179281456,"peer_address_by_id":{},"peer_metadata_by_id":{},"this_peer_id":4166060179281456}
{}

Copilot uses AI. Check for mistakes.
@@ -0,0 +1 @@
{} No newline at end of file
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be local/generated data committed accidentally (empty aliases DB). If it’s runtime state, it should not be versioned; remove it from the PR and add an ignore rule for this path.

Suggested change
{}
{
"_meta": {
"description": "Aliases configuration file. This file is intentionally versioned; add alias mappings under the 'aliases' key.",
"version": 1
},
"aliases": {
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
"vitest.disableWorkspaceWarning": true,
"ndjson.port": 7700
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ndjson.port setting looks like a developer-local VS Code extension preference rather than a project-wide requirement. Consider removing it from the repo-level .vscode/settings.json (or documenting why it must be shared) to avoid forcing local preferences on contributors.

Suggested change
"vitest.disableWorkspaceWarning": true,
"ndjson.port": 7700
"vitest.disableWorkspaceWarning": true

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants