github-pr-automation
Version:
MCP server and CLI for automated GitHub PR management, review resolution, and workflow optimization
439 lines (353 loc) • 12.3 kB
Markdown
# User Preference Hints for AI Agents
## Overview
Tool schemas can include metadata hints to help AI agents identify which parameters are likely user preferences worth remembering, vs. situational parameters that change each invocation.
## Schema Annotations
### In Tool Descriptions (MCP Standard)
```typescript
// MCP tool registration
{
name: "get_failing_tests",
description: "Analyze PR CI failures and provide fix instructions",
inputSchema: {
type: "object",
required: ["pr"], // Only PR is required
properties: {
pr: {
type: "string",
description: "PR identifier (owner/repo#123)"
},
wait: {
type: "boolean",
description: "Wait for CI completion (default: false)",
default: false
},
bail_on_first: {
type: "boolean",
description: "Stop at first failure when waiting. " +
"💾 User preference: Some users prefer fast feedback, " +
"others want to see all failures at once. " +
"(default: true, overridden by user preference if set)",
default: true
},
page_size: {
type: "number",
description: "Results per page. " +
"💾 User preference: Power users often prefer larger pages. " +
"(default: 10, overridden by user preference if set)",
default: 10,
minimum: 1,
maximum: 50
}
}
}
}
// Zod schema with optional preference-worthy params
export const GetFailingTestsSchema = z.object({
pr: z.string(),
wait: z.boolean().optional().default(false),
bail_on_first: z.boolean().optional(), // Optional - allows 3-level precedence
page: z.number().int().min(1).optional().default(1),
page_size: z.number().int().min(1).max(50).optional() // Optional
});
```
```
### Preference Hint Convention
Use the 💾 emoji or "User preference:" prefix in descriptions to signal:
**This parameter is likely a personal preference worth remembering**
## Examples by Tool
### get_failing_tests
| Parameter | Type | Preference? | Reasoning |
|-----------|------|-------------|-----------|
| `pr` | string | ❌ No | Changes every call |
| `wait` | boolean | ⚠️ Maybe | Depends on urgency |
| `bail_on_first` | boolean | ✅ Yes | Workflow preference |
| `page` | number | ❌ No | Pagination state |
| `page_size` | number | ✅ Yes | Display preference |
### find_unresolved_comments
| Parameter | Type | Preference? | Reasoning |
|-----------|------|-------------|-----------|
| `pr` | string | ❌ No | Changes every call |
| `include_bots` | boolean | ✅ Yes | Strong preference |
| `exclude_authors` | array | ⚠️ Maybe | May vary by project |
| `page` | number | ❌ No | Pagination state |
| `page_size` | number | ✅ Yes | Display preference |
| `sort` | enum | ✅ Yes | Workflow preference |
### manage_stacked_prs
| Parameter | Type | Preference? | Reasoning |
|-----------|------|-------------|-----------|
| `base_pr` | string | ❌ No | Situational |
| `dependent_pr` | string | ❌ No | Situational |
| `auto_fix` | boolean | ✅ Yes | Trust level preference |
| `use_onto` | boolean | ⚠️ Maybe | Some always want it |
| `page_size` | number | ✅ Yes | Display preference |
### rebase_after_squash_merge
| Parameter | Type | Preference? | Reasoning |
|-----------|------|-------------|-----------|
| `pr` | string | ❌ No | Situational |
| `upstream_pr` | string | ❌ No | Situational |
| `target_branch` | string | ⚠️ Maybe | Often "main" |
## AI Agent Behavior
### On First Use
```
User: "Get failing tests for owner/repo#123"
AI: Uses default bail_on_first: true
AI: "I found 1 failing test. (Note: I stopped at the first failure.
If you prefer to see all failures at once, let me know!)"
```
### Learning Preference
```
User: "No, show me all failures"
AI: Calls again with bail_on_first: false
AI: *stores in memory* "User prefers bail_on_first: false"
Later...
User: "Get failing tests for owner/repo#456"
AI: *remembers* Uses bail_on_first: false automatically
AI: "Found 5 failing tests (showing all failures as you prefer)"
```
### Confirming Preference
```
AI: "I notice you've set include_bots: false the last 3 times.
Would you like me to always exclude bot comments by default?"
User: "Yes"
AI: *stores as strong preference*
```
## Response Hints
Tools can also return hints in responses:
```typescript
{
"status": "failed",
"failures": [...],
"preferences_detected": {
"bail_on_first": {
"current_value": true,
"hint": "You used bail_on_first=true. This is typically a user preference.",
"alternatives": ["Set to false to see all failures at once"]
}
}
}
```
## Implementation in Tool Handlers
### Schema Definition
```typescript
export const GetFailingTestsSchema = z.object({
pr: z.string()
.describe("PR identifier (owner/repo#123)"),
wait: z.boolean()
.default(false)
.describe("Wait for CI completion"),
bail_on_first: z.boolean()
.default(true)
.describe(
"Stop at first failure when waiting. " +
"💾 User preference: Fast feedback vs. complete results"
),
page_size: z.number()
.int()
.min(1)
.max(50)
.default(10)
.describe(
"Results per page. " +
"💾 User preference: Power users often prefer 20-50"
)
});
// Metadata for AI agents
export const GetFailingTestsMetadata = {
preferenceHints: {
bail_on_first: {
type: "workflow",
description: "User's preferred failure reporting style",
learnFrom: "repeated_usage"
},
page_size: {
type: "display",
description: "User's preferred result pagination size",
learnFrom: "repeated_usage"
}
}
};
```
## Parameter Precedence (3-Level System)
**What user preferences do**: Override tool defaults for optional arguments ONLY.
**Precedence order** (highest to lowest):
1. **Explicit argument from LLM/agent** - ALWAYS WINS, no exceptions
2. **User preference** - Overrides tool default for optional arguments
3. **Tool default** - Base fallback
**Critical rules**:
- ✅ Preferences override **tool defaults** for optional parameters
- ❌ Preferences NEVER override **explicit arguments** from LLM/agent
- 💡 Think of preferences as "better defaults" that the LLM can still override
### Implementation
```typescript
function resolveParameterValue<T>(
paramName: string,
explicitValue: T | undefined, // What LLM/agent provided
userPreferences: Record<string, any>,
toolDefault: T
): T {
// 1. Explicit argument from LLM/agent ALWAYS wins
if (explicitValue !== undefined) {
return explicitValue; // LLM said so, use it!
}
// 2. No explicit argument? Check user preference
// (This is where preference overrides the default)
if (paramName in userPreferences) {
return userPreferences[paramName]; // User's "better default"
}
// 3. No explicit arg, no preference? Use tool default
return toolDefault;
}
// Usage in tool handler
export async function handleGetFailingTests(args: GetFailingTestsInput) {
const prefs = await loadUserPreferences('get_failing_tests');
const effectiveArgs = {
pr: args.pr, // Required, no default
wait: args.wait ?? false, // No preference support
bail_on_first: resolveParameterValue(
'bail_on_first',
args.bail_on_first, // undefined if not provided
prefs,
true // tool default
),
page_size: resolveParameterValue(
'page_size',
args.page_size,
prefs,
10 // tool default
)
};
// ... use effectiveArgs
}
```
## Config File Support (Optional)
```json
// ~/.resolve-pr-mcp/preferences.json
{
"version": "1.0",
"preferences": {
"get_failing_tests": {
"bail_on_first": false, // User prefers seeing all failures
"page_size": 20 // User prefers larger pages
},
"find_unresolved_comments": {
"include_bots": false, // User filters out bots
"sort": "by_file" // User prefers file-based sorting
}
},
"learned_from": "AI agent suggestions",
"updated_at": "2024-01-15T10:30:00Z"
}
```
## Example Scenarios
### Scenario 1: No preference, use default
```typescript
// User call
get_failing_tests({ pr: "owner/repo#123" })
// No bail_on_first provided, no preference stored
// Result: Uses tool default (true)
effectiveArgs.bail_on_first === true
```
### Scenario 2: Preference exists, use it
```typescript
// User call
get_failing_tests({ pr: "owner/repo#123" })
// No bail_on_first provided, but preference exists
// Preference: { "bail_on_first": false }
// Result: Uses user preference (false)
effectiveArgs.bail_on_first === false
```
### Scenario 3: Explicit argument from LLM overrides preference
```typescript
// LLM/agent call (explicit argument provided)
get_failing_tests({
pr: "owner/repo#123",
bail_on_first: true // LLM explicitly said true
})
// User's stored preference: { "bail_on_first": false }
// Result: LLM's explicit argument wins (true)
// Preference is ignored because LLM was explicit
effectiveArgs.bail_on_first === true
```
### Scenario 4: LLM can situationally override preference
```typescript
// User's stored preference: bail_on_first = false (see all failures)
// But LLM decides this time user wants quick feedback
get_failing_tests({
pr: "owner/repo#123",
bail_on_first: true // LLM explicitly overrides preference
})
// Result: true (LLM's explicit choice)
// Next time, LLM omits the argument (lets preference apply)
get_failing_tests({ pr: "owner/repo#456" })
// No explicit bail_on_first provided
// Result: false (user's preference)
```
## Example: Complete Flow
### 1. First Interaction
```
User: "Check PR #123 for failing tests"
AI: Uses defaults (bail_on_first: true, page_size: 10)
```
### 2. User Correction
```
User: "Show all failures, not just the first"
AI: Learns bail_on_first: false preference
AI: *stores in conversation memory*
```
### 3. Pattern Recognition
```
AI: After 3 uses with same preference
AI: "I notice you prefer seeing all test failures at once.
Should I make this your default?"
```
### 4. Automatic Application
```
User: "Check PR #456 for failing tests"
AI: *applies remembered preference*
AI: Uses bail_on_first: false automatically
AI: "Checking all failures (as you prefer)..."
```
### 5. Situational Override
```
User: "Quick check on PR #789, just show first failure"
AI: *recognizes situational override*
AI: Uses bail_on_first: true this once
AI: "Using fast mode for this check..."
```
## Benefits
1. **AI learns naturally** from user corrections
2. **No database required** - preferences in conversation memory or simple file
3. **Always overridable** - situational needs trump preferences
4. **Clear signals** - 💾 emoji makes it obvious to both AI and users
5. **Self-documenting** - hints explain why something is preference-worthy
## Guideline: What Makes a Good Preference Hint?
### ✅ Good Preference Candidates
- **Workflow style**: Fast vs. thorough (`bail_on_first`)
- **Display preferences**: Pagination sizes
- **Noise tolerance**: Include/exclude bot comments
- **Trust level**: Auto-fix vs. manual review
- **Format preferences**: JSON vs. human-readable
### ❌ Not Preference Candidates
- **Context-specific**: PR identifiers, branch names
- **State**: Page numbers, cursors
- **One-time**: Special flags for specific situations
- **Variable by project**: Might change per repo
### ⚠️ Maybe Preferences
- **Common patterns**: Target branch (often "main" but not always)
- **Project-specific**: Reviewer preferences
- **Frequency-dependent**: Things used rarely
## Summary
**What user preferences are**:
- Better defaults that override tool defaults
- Apply ONLY to optional parameters
- Apply ONLY when LLM doesn't provide explicit argument
**What user preferences are NOT**:
- Not overrides for explicit LLM arguments
- Not mandatory settings
- Not stored server-side (just local config file)
**Implementation**:
- Add 💾 emoji to mark preference-worthy parameters
- AI agents learn and store preferences
- Simple config file (optional)
- Explicit LLM arguments ALWAYS win
- Keeps MCP server stateless