Home/
Part XIII — Expert Mode: Systems, Agents, and Automation/38. Building a Code-Change Agent Safely/38.1 The "proposal-only" agent pattern
38.1 The "proposal-only" agent pattern
Overview and links for this section of the guide.
The Concept
A proposal-only agent can analyze and recommend, but cannot modify anything. It produces human-readable plans that humans execute.
Agent Input: "Fix the login timeout bug"
Agent Output:
1. Read src/auth/session.ts (lines 45-80)
2. The timeout is hardcoded to 1 hour at line 67
3. RECOMMENDATION: Change `3600000` to `configurable timeout`
4. Also update: src/config/defaults.ts to add new config
5. Add test: tests/auth/session.test.ts for timeout behavior
The agent doesn't write code. It tells a human what to do.
Implementation
// proposal-agent.ts
const PROPOSAL_SYSTEM_PROMPT = `You are a code analysis agent.
Your job is to analyze issues and propose solutions.
You can READ files but you CANNOT modify them.
You PROPOSE changes; humans IMPLEMENT them.
For each proposal:
1. Analyze the problem thoroughly
2. Identify all files that need changes
3. Describe exactly what changes are needed
4. Explain the reasoning
5. Note any risks or alternatives
Output a structured proposal that a developer can follow.`;
export class ProposalAgent {
private tools = [
{ name: 'read_file', description: 'Read file contents' },
{ name: 'search_code', description: 'Search for patterns' },
{ name: 'list_files', description: 'List directory contents' },
// NO write tools - intentionally limited
];
async createProposal(issue: Issue): Promise {
const response = await this.model.generate({
systemInstruction: PROPOSAL_SYSTEM_PROMPT,
contents: [{ role: 'user', parts: [{ text: issue.description }] }],
tools: this.tools.map(t => ({
functionDeclarations: [t]
}))
});
return this.parseProposal(response);
}
private parseProposal(response: any): Proposal {
// Extract structured proposal from response
return {
summary: response.summary,
affectedFiles: response.files,
changes: response.changes.map(c => ({
file: c.file,
description: c.description,
reasoning: c.reasoning,
priority: c.priority
})),
risks: response.risks,
alternatives: response.alternatives
};
}
}
Proposal Format
interface Proposal {
// High-level summary
summary: string;
// Files to be modified
affectedFiles: Array<{
path: string;
action: 'modify' | 'create' | 'delete';
reason: string;
}>;
// Specific changes
changes: Array<{
file: string;
location: string; // "lines 45-67" or "function handleLogin"
currentCode: string; // What's there now
proposedChange: string; // What should change (in natural language)
reasoning: string; // Why this change
}>;
// Risk assessment
risks: Array<{
description: string;
severity: 'low' | 'medium' | 'high';
mitigation: string;
}>;
// Alternative approaches
alternatives: Array<{
description: string;
tradeoffs: string;
}>;
}
// Example proposal
const exampleProposal: Proposal = {
summary: "Fix session timeout by making it configurable",
affectedFiles: [
{ path: "src/auth/session.ts", action: "modify", reason: "Contains hardcoded timeout" },
{ path: "src/config/defaults.ts", action: "modify", reason: "Add config option" },
{ path: "tests/auth/session.test.ts", action: "modify", reason: "Add timeout tests" }
],
changes: [
{
file: "src/auth/session.ts",
location: "Line 67, inside createSession()",
currentCode: "const timeout = 3600000;",
proposedChange: "Read timeout from config.session.timeout with fallback to 1 hour",
reasoning: "Allows operators to adjust timeout without code changes"
}
],
risks: [
{
description: "Very short timeouts could lock users out",
severity: "medium",
mitigation: "Add minimum timeout validation (e.g., 5 minutes)"
}
],
alternatives: [
{
description: "Sliding window timeout that resets on activity",
tradeoffs: "More complex but better UX for active users"
}
]
};
Review Interface
// proposal-review.tsx
function ProposalReview({ proposal }: { proposal: Proposal }) {
return (
{proposal.summary}
Affected Files ({proposal.affectedFiles.length})
{proposal.affectedFiles.map(f => (
{f.action === 'modify' && '📝'}
{f.action === 'create' && '✨'}
{f.action === 'delete' && '🗑️'}
{f.path}
))}
Proposed Changes
{proposal.changes.map(c => (
{c.file} at {c.location}
{c.currentCode}
→ {c.proposedChange}
Reason: {c.reasoning}
))}
Risks
{proposal.risks.map(r => (
{r.severity}
{r.description}
Mitigation: {r.mitigation}
))}
);
}
Proposal-Only is Great for Learning
Proposal agents teach your team about AI capabilities without risk. Developers learn what the AI can analyze, and the AI learns from which proposals get accepted.