Add autonomous continuation mode - #222
Conversation
| initialImages?: ImageContent[]; | ||
| } | ||
|
|
||
| function latestGateAttempt(status: AgentAutonomousStatus): number { |
There was a problem hiding this comment.
🟡 Medium modes/print-mode.ts:29
latestGateAttempt() computes the maximum across all entries in status.gateAttempts, including stale retry counts for commands other than status.lastGateFailure.command. With multiple quality-gate commands, a later command can fail several times and then an earlier command can fail on its first retry. In that state lastGateFailure.attempt is 1, but latestGateAttempt() returns the stale higher count from the later command. As a result, waitForPrintModeIdleWithAutonomousGates() sees attempt <= lastPromptedGateAttempt and stops retrying before the new failure is prompted, or shouldContinuePrintModeAutonomousGates() sees the count exceed maxRetries and stops even though the failing command has only been retried once. Consider tracking the attempt count only for the command in lastGateFailure.command instead of taking the maximum across all commands.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/print-mode.ts around line 29:
`latestGateAttempt()` computes the maximum across all entries in `status.gateAttempts`, including stale retry counts for commands other than `status.lastGateFailure.command`. With multiple quality-gate commands, a later command can fail several times and then an earlier command can fail on its first retry. In that state `lastGateFailure.attempt` is `1`, but `latestGateAttempt()` returns the stale higher count from the later command. As a result, `waitForPrintModeIdleWithAutonomousGates()` sees `attempt <= lastPromptedGateAttempt` and stops retrying before the new failure is prompted, or `shouldContinuePrintModeAutonomousGates()` sees the count exceed `maxRetries` and stops even though the failing command has only been retried once. Consider tracking the attempt count only for the command in `lastGateFailure.command` instead of taking the maximum across all commands.
| ); | ||
| } | ||
|
|
||
| async function waitForPrintModeIdleWithAutonomousGates( |
There was a problem hiding this comment.
🟡 Medium modes/print-mode.ts:58
waitForPrintModeIdleWithAutonomousGates sends follow-up gate-retry prompts via session.prompt(...) but never increments status.continuationsUsed, so autonomousLimitsReached()'s maxContinuations check never advances during print-mode retries. The --autonomous-max-continuations limit is silently ignored and the loop can continue more times than configured. Consider incrementing continuationsUsed (or routing through nextAutonomousContinuation) before each follow-up prompt.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/modes/print-mode.ts around line 58:
`waitForPrintModeIdleWithAutonomousGates` sends follow-up gate-retry prompts via `session.prompt(...)` but never increments `status.continuationsUsed`, so `autonomousLimitsReached()`'s `maxContinuations` check never advances during print-mode retries. The `--autonomous-max-continuations` limit is silently ignored and the loop can continue more times than configured. Consider incrementing `continuationsUsed` (or routing through `nextAutonomousContinuation`) before each follow-up prompt.
| return false; | ||
| } | ||
|
|
||
| if (this._queueAutonomousContinuationForThresholdCompaction(context.message)) { |
There was a problem hiding this comment.
🟠 High core/agent-session.ts:1379
_shouldStopForThresholdCompaction() calls _queueAutonomousContinuationForThresholdCompaction() and returns true, but the same assistant message is processed again in the later agent_end compaction path where _checkCompaction() also calls _queueAutonomousContinuationForThresholdCompaction(). This queues two identical autonomous follow-up user messages and increments state.continuationsUsed twice for a single turn. After compaction, _runPostCompactionContinue() sends both messages, causing duplicate autonomous prompts and premature exhaustion of the continuation limit. Consider gating the autonomous continuation so it is only queued once per assistant message.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/agent-session.ts around line 1379:
`_shouldStopForThresholdCompaction()` calls `_queueAutonomousContinuationForThresholdCompaction()` and returns `true`, but the same assistant message is processed again in the later `agent_end` compaction path where `_checkCompaction()` also calls `_queueAutonomousContinuationForThresholdCompaction()`. This queues two identical autonomous follow-up user messages and increments `state.continuationsUsed` twice for a single turn. After compaction, `_runPostCompactionContinue()` sends both messages, causing duplicate autonomous prompts and premature exhaustion of the continuation limit. Consider gating the autonomous continuation so it is only queued once per assistant message.
| await this.agent.waitForIdle(); | ||
| await this.agent.prompt(messages); | ||
| await this.waitForRetry(); | ||
| await this._waitForPostCompactionContinuations(); |
There was a problem hiding this comment.
🟡 Medium core/agent-session.ts:2320
prompt() calls await this._waitForPostCompactionContinuations(), but the scheduled continuation path swallows all failures with .catch(() => {}). If an agent_end listener keeps the previous run alive past the hard-coded 100 ms delay, _runPostCompactionContinue() calls this.agent.prompt()/this.agent.continue() while activeRun is still set, which throws Agent is already processing. That exception is silently dropped, so prompt() returns successfully even though the continuation was never executed. Consider awaiting the continuation directly (or propagating its error) instead of scheduling with a fire-and-forget .catch(() => {}), so a still-active run surfaces as a real error rather than a silent no-op.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/agent-session.ts around line 2320:
`prompt()` calls `await this._waitForPostCompactionContinuations()`, but the scheduled continuation path swallows all failures with `.catch(() => {})`. If an `agent_end` listener keeps the previous run alive past the hard-coded 100 ms delay, `_runPostCompactionContinue()` calls `this.agent.prompt()`/`this.agent.continue()` while `activeRun` is still set, which throws `Agent is already processing`. That exception is silently dropped, so `prompt()` returns successfully even though the continuation was never executed. Consider awaiting the continuation directly (or propagating its error) instead of scheduling with a fire-and-forget `.catch(() => {})`, so a still-active run surfaces as a real error rather than a silent no-op.
| themes: resolveCliPaths(cwd, parsed.themes), | ||
| noThemes: parsed.noThemes, | ||
| noContextFiles: parsed.noContextFiles, | ||
| autonomous: parsed.autonomous |
There was a problem hiding this comment.
🟡 Medium src/main.ts:666
runtimeConfigFromArgs only builds the autonomous config object when parsed.autonomous is truthy, so all --autonomous-* CLI flags are discarded whenever the session doesn't start in autonomous mode. For example, --autonomous-max-turns 1 used with interactive mode and then /autonomous on leaves config.autonomous as undefined, so the runtime initializes with default limits and the user's configured overrides are never applied. Consider populating the autonomous object whenever any autonomous* field is set, not just when parsed.autonomous is true.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/main.ts around line 666:
`runtimeConfigFromArgs` only builds the `autonomous` config object when `parsed.autonomous` is truthy, so all `--autonomous-*` CLI flags are discarded whenever the session doesn't start in autonomous mode. For example, `--autonomous-max-turns 1` used with interactive mode and then `/autonomous on` leaves `config.autonomous` as `undefined`, so the runtime initializes with default limits and the user's configured overrides are never applied. Consider populating the `autonomous` object whenever any `autonomous*` field is set, not just when `parsed.autonomous` is true.
| return "failed"; | ||
| } | ||
| for (const command of state.gates.commands) { | ||
| const currentSnapshot = captureGitWorktreeSnapshot(cwd); |
There was a problem hiding this comment.
🟡 Medium core/autonomous.ts:252
runAutonomousQualityGates captures currentSnapshot before the gate command runs and then stores it in lastGateFailureSnapshot when the gate fails. If the failing gate itself mutates the worktree (e.g. writes a lockfile, formats files, updates fixtures), the stored snapshot reflects the pre-command state. On the next call, captureGitWorktreeSnapshot returns the post-mutation state, so the equality check fails and the guard reruns the gate even though the assistant has not changed anything. This defeats the "do not rerun until the workspace changes" check and can repeatedly execute expensive or side-effecting gates until retries are exhausted. Consider capturing the snapshot after the gate command finishes so that lastGateFailureSnapshot reflects the state the assistant must change before the gate is rerun.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/autonomous.ts around line 252:
`runAutonomousQualityGates` captures `currentSnapshot` before the gate command runs and then stores it in `lastGateFailureSnapshot` when the gate fails. If the failing gate itself mutates the worktree (e.g. writes a lockfile, formats files, updates fixtures), the stored snapshot reflects the pre-command state. On the next call, `captureGitWorktreeSnapshot` returns the post-mutation state, so the equality check fails and the guard reruns the gate even though the assistant has not changed anything. This defeats the "do not rerun until the workspace changes" check and can repeatedly execute expensive or side-effecting gates until retries are exhausted. Consider capturing the snapshot after the gate command finishes so that `lastGateFailureSnapshot` reflects the state the assistant must change before the gate is rerun.
| if (status.status !== 0 || typeof status.stdout !== "string") { | ||
| return undefined; | ||
| } | ||
| const diff = spawnSync("git", ["--no-optional-locks", "diff", "--no-ext-diff", "--binary", "HEAD", ...pathspec], { |
There was a problem hiding this comment.
🟡 Medium core/autonomous.ts:347
captureGitWorktreeSnapshot sets diff to "" whenever git diff HEAD fails. In a fresh repository with no commits, HEAD is unborn so git diff HEAD fails on every snapshot. Because the status output only records pathnames (?? path) and not file contents, editing an existing untracked file after autonomous mode starts does not change either status or diff, so hasGitWorktreeChangedSinceBaseline returns false for real edits. This makes autonomous mode treat genuine work as "no terminal evidence" and keep issuing continuation prompts in fresh repos until a limit is hit. Consider falling back to a diff of the working tree against the index (or an empty tree) when HEAD is unborn so untracked-file edits are reflected in the snapshot.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/autonomous.ts around line 347:
`captureGitWorktreeSnapshot` sets `diff` to `""` whenever `git diff HEAD` fails. In a fresh repository with no commits, `HEAD` is unborn so `git diff HEAD` fails on every snapshot. Because the `status` output only records pathnames (`?? path`) and not file contents, editing an existing untracked file after autonomous mode starts does not change either `status` or `diff`, so `hasGitWorktreeChangedSinceBaseline` returns `false` for real edits. This makes autonomous mode treat genuine work as "no terminal evidence" and keep issuing continuation prompts in fresh repos until a limit is hit. Consider falling back to a diff of the working tree against the index (or an empty tree) when `HEAD` is unborn so untracked-file edits are reflected in the snapshot.
|
Closing as superseded by #278. The |
Summary
--autonomousand/autonomous on|off|status--autonomous-gate,--autonomous-gate-retries, and--autonomous-gate-timeout-ms/goalbehavior: goal continuations run first, autonomous continuation is a later fallbackHeadless / Verifiers
autonomous,autonomous_gates,autonomous_gate_retries, andautonomous_gate_timeout_msand forwards them to the Prime Agent CLIverifiersPR refactor(coding-agent): delete unreachable empty-selector auto-cancel timers #1828 with--harness.id prime-agent-headless-v1,--harness.autonomous True, and--harness.autonomous-gates ["./verify-public.sh"]Testing
npm --prefix packages/coding-agent test -- suite/agent-session-autonomous.test.ts args.test.ts./node_modules/.bin/tsgo --noEmitnpm run checkuv run --isolated --no-project --with-editable /Users/milkkarten/Research/verifiers-pr1828 --with-editable /Users/milkkarten/Research/prime-agent-headless-v1 --with pytest python -m pytest tests/test_import.py -qautonomous = true,autonomous_gates = ["./verify-public.sh"],labels = ["programbench"], andX-Prime-Team-IDpresentNotes