Skip to content

Commit 2b3385a

Browse files
feat: add Amp (Sourcegraph) as first-class agent with SDK + CLI support (#291)
* feat: add Amp (Sourcegraph) as first-class agent with SDK + CLI support - Add 'amp' to AgentType union and AGENTS config (#235) - Implement runAmpAgent() using @sourcegraph/amp-sdk execute() async generator - Add CLI fallback via amp --execute --stream-json for environments without SDK - Add --amp-mode flag (smart/rush/deep) for agent mode selection (#238) - Wire ampMode through CLI → RunCommandOptions → LoopOptions → AgentRunOptions (#236) - Export AmpMode type from package index for SDK consumers - Amp ranked 2nd in agent preference (after Claude Code) - Update tests for 6-agent detection and amp preference order Closes #234, #235, #236, #238 Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> * fix: address code review — security, CLI flags, and memory guard - Fix dangerouslyAllowAll defaulting to true → false (security) - Register --amp-mode flag on auto and template commands - Add maxOutputBytes truncation guard to runAmpCli Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> * fix: SDK output guard, optional dep, and timeout exit code - Add maxOutputBytes truncation guard to runAmpAgent SDK path - Move @sourcegraph/amp-sdk to optionalDependencies to avoid bundling Amp CLI binary for all users and prevent false-positive agent detection - Return exit code 124 on timeout (TimeoutError/AbortError) in SDK path, matching CLI path and runAgent behavior Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> * fix: append output before truncation guard to prevent byte desync Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> * fix: forward env to Amp SDK and use npm install hint - Pass options.env to SDK execute() (SDK supports env natively) - Replace curl|bash install hint with npm install -g @sourcegraph/amp Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> * chore: bump version to 0.4.3 Amp-Thread-ID: https://ampcode.com/threads/T-019ce298-ab61-760e-b725-803364016be5 Co-authored-by: Amp <amp@ampcode.com> --------- Co-authored-by: Amp <amp@ampcode.com>
1 parent 8d382d0 commit 2b3385a

8 files changed

Lines changed: 396 additions & 12 deletions

File tree

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ralph-starter",
3-
"version": "0.4.2",
3+
"version": "0.4.3",
44
"description": "Ralph Wiggum made easy. One command to run autonomous AI coding loops with auto-commit, PRs, and Docker sandbox.",
55
"main": "dist/index.js",
66
"bin": {
@@ -86,6 +86,9 @@
8686
"yaml": "^2.7.0",
8787
"zod": "^4.3.6"
8888
},
89+
"optionalDependencies": {
90+
"@sourcegraph/amp-sdk": "0.1.0-20260312122132-g783443e"
91+
},
8992
"devDependencies": {
9093
"@biomejs/biome": "^2.3.13",
9194
"@commitlint/cli": "^20.3.1",

pnpm-lock.yaml

Lines changed: 158 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ program
6363
.option('--docker', 'Run in Docker sandbox (coming soon)')
6464
.option('--prd <file>', 'Read tasks from a PRD markdown file')
6565
.option('--max-iterations <n>', 'Maximum loop iterations (auto-calculated if not specified)')
66-
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw)')
66+
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
6767
.option('--model <name>', 'Model to use (e.g., claude-sonnet-4-5-20250929, claude-opus-4-6)')
68+
.option(
69+
'--amp-mode <mode>',
70+
'Amp agent mode: smart (frontier), rush (fast), deep (extended reasoning)'
71+
)
6872
.option('--from <source>', 'Fetch spec from source (file, url, github, todoist, linear, notion)')
6973
.option('--project <name>', 'Project/repo name for --from integrations')
7074
.option('--label <name>', 'Label filter for --from integrations')
@@ -315,6 +319,10 @@ program
315319
.option('--dry-run', 'Preview mode - show tasks without executing')
316320
.option('--skip-pr', 'Skip PR creation (commit only)')
317321
.option('--agent <name>', 'Specify agent to use')
322+
.option(
323+
'--amp-mode <mode>',
324+
'Amp agent mode: smart (frontier), rush (fast), deep (extended reasoning)'
325+
)
318326
.option('--validate', 'Run validation after each task', true)
319327
.option('--no-validate', 'Skip validation')
320328
.option('--max-iterations <n>', 'Max iterations per task (default: 15)')
@@ -407,7 +415,11 @@ program
407415
.option('--pr', 'Create a pull request when done')
408416
.option('--validate', 'Run tests/lint/build after each iteration')
409417
.option('--max-iterations <n>', 'Maximum loop iterations')
410-
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw)')
418+
.option('--agent <name>', 'Specify agent (claude-code, cursor, codex, opencode, openclaw, amp)')
419+
.option(
420+
'--amp-mode <mode>',
421+
'Amp agent mode: smart (frontier), rush (fast), deep (extended reasoning)'
422+
)
411423
.action(async (action: string | undefined, args: string[], options) => {
412424
await templateCommand(action, args, options);
413425
});

src/commands/run.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ export interface RunCommandOptions {
310310
// Swarm mode
311311
swarm?: boolean;
312312
strategy?: 'race' | 'consensus' | 'pipeline';
313+
// Amp options
314+
ampMode?: 'smart' | 'rush' | 'deep';
313315
}
314316

315317
export async function runCommand(
@@ -971,6 +973,11 @@ export async function runCommand(
971973
console.log();
972974
console.log(chalk.yellow('Please install one of these:'));
973975
console.log(chalk.gray(' Claude Code: npm install -g @anthropic-ai/claude-code'));
976+
console.log(
977+
chalk.gray(
978+
' Amp: npm install -g @sourcegraph/amp (or see https://ampcode.com/install)'
979+
)
980+
);
974981
console.log(chalk.gray(' Cursor: https://cursor.sh'));
975982
console.log(chalk.gray(' Codex: npm install -g codex'));
976983
console.log(chalk.gray(' OpenCode: npm install -g opencode'));
@@ -1426,6 +1433,7 @@ Focus on one task at a time. After completing a task, update IMPLEMENTATION_PLAN
14261433
designImagePath,
14271434
visualValidation,
14281435
figmaScreenshotPaths,
1436+
ampMode: options.ampMode,
14291437
};
14301438

14311439
// Swarm mode: run with multiple agents in parallel

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type { LLMProvider } from './llm/providers.js';
2929
export { OPENROUTER_MODEL_ALIASES, resolveOpenRouterModel } from './llm/providers.js';
3030
export type { AcceptanceCriteria, AcceptanceCriterion } from './loop/acceptance-criteria.js';
3131
export { extractAcceptanceCriteria } from './loop/acceptance-criteria.js';
32-
export type { Agent, AgentType } from './loop/agents.js';
32+
export type { Agent, AgentType, AmpMode } from './loop/agents.js';
3333
export { detectAvailableAgents, detectBestAgent } from './loop/agents.js';
3434
export type { CircuitBreakerConfig, CircuitBreakerState } from './loop/circuit-breaker.js';
3535
// Loop

src/loop/__tests__/agents.test.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ describe('agents', () => {
8787
.mockRejectedValueOnce(new Error('not found')) // cursor
8888
.mockRejectedValueOnce(new Error('not found')) // codex
8989
.mockRejectedValueOnce(new Error('not found')) // opencode
90-
.mockRejectedValueOnce(new Error('not found')); // openclaw
90+
.mockRejectedValueOnce(new Error('not found')) // openclaw
91+
.mockRejectedValueOnce(new Error('not found')); // amp
9192

9293
const agents = await detectAvailableAgents();
9394

94-
expect(agents).toHaveLength(5);
95+
expect(agents).toHaveLength(6);
9596
expect(agents.find((a) => a.type === 'claude-code')?.available).toBe(true);
9697
expect(agents.find((a) => a.type === 'cursor')?.available).toBe(false);
9798
});
@@ -121,15 +122,30 @@ describe('agents', () => {
121122
expect(agent?.type).toBe('claude-code');
122123
});
123124

124-
it('should fall back to cursor if claude-code is not available', async () => {
125+
it('should fall back to amp if claude-code is not available', async () => {
126+
mockExeca
127+
.mockRejectedValueOnce(new Error('not found')) // claude-code
128+
.mockRejectedValueOnce(new Error('not found')) // cursor
129+
.mockRejectedValueOnce(new Error('not found')) // codex
130+
.mockRejectedValueOnce(new Error('not found')) // opencode
131+
.mockRejectedValueOnce(new Error('not found')) // openclaw
132+
.mockResolvedValueOnce({ stdout: '1.0.0', exitCode: 0 } as any); // amp
133+
134+
const agent = await detectBestAgent();
135+
expect(agent?.type).toBe('amp');
136+
});
137+
138+
it('should prefer amp over cursor', async () => {
125139
mockExeca
126140
.mockRejectedValueOnce(new Error('not found')) // claude-code
127141
.mockResolvedValueOnce({ stdout: '1.0.0', exitCode: 0 } as any) // cursor
128142
.mockRejectedValueOnce(new Error('not found')) // codex
129-
.mockRejectedValueOnce(new Error('not found')); // opencode
143+
.mockRejectedValueOnce(new Error('not found')) // opencode
144+
.mockRejectedValueOnce(new Error('not found')) // openclaw
145+
.mockResolvedValueOnce({ stdout: '1.0.0', exitCode: 0 } as any); // amp
130146

131147
const agent = await detectBestAgent();
132-
expect(agent?.type).toBe('cursor');
148+
expect(agent?.type).toBe('amp');
133149
});
134150
});
135151

0 commit comments

Comments
 (0)