auto-run-goal-driven: 34 tasks across 2026-05-25-Goal-Driven-Auto-Run-Mode/Phase-01-Goal-Driven-Core-Engine, 2026-05-25-Goal-Driven-Auto-Run-Mode/Phase-02-Engine-Integration +3 more#1045
Conversation
…exit evaluator) Phase 01 of Goal-Driven Auto Run mode — pure, dependency-free logic with tests. - src/shared/goalDriven/types.ts: GoalRunConfig, GoalMarkers, GoalIterationRecord, GoalExitReason, GoalExitDecision, STALL_THRESHOLD (=3, mirrors MAX_CONSECUTIVE_NO_CHANGES in useBatchRunner.ts). - src/shared/goalDriven/goalMarkers.ts: parseGoalMarkers() — whitespace-tolerant regex parsing of <!-- maestro:progress|goal-complete|deadlock --> markers (last occurrence wins; clamp 0-100; progress 100 implies complete). - src/shared/goalDriven/goalExitEvaluator.ts: evaluateGoalExit() — priority order completion > deadlock > max-iterations > stall > continue; defensive missing-progress normalization for stall detection. - Tests: 43 cases across marker parser, exit evaluator, and a narrative simulation that drives the control loop 0->45->70->100 and the stall/deadlock scenarios. Full suite green (29,592 passed).
Phase 02 of Goal-Driven Auto Run: makes a goal run executable end-to-end.
- types: add goalConfig? to BatchRunConfig (the goal-mode discriminator) and
goal* progress fields to BatchRunState; seed DEFAULT_BATCH_STATE defaults.
Thread goal fields through UpdateProgressPayload + the UPDATE_PROGRESS reducer
case + the useBatchBroadcast debounce diff so goal state reaches the store/web.
- prompt: add src/prompts/autorun-goal.md (synopsis-first; pursue {{GOAL}},
one iteration of real progress then EXIT, emit progress/goal-complete/deadlock
markers matching parseGoalMarkers). Register autorun-goal in promptDefinitions.
Add {{GOAL}} / {{GOAL_EXIT_CRITERIA}} template variables.
- engine: add useGoalRunner — mirrors useBatchRunner's lifecycle (time tracking,
power inhibit, stats, history, broadcast, flush-state kill guard) but drives a
goal loop: build prompt, spawn via onSpawnAgent (SSH preserved), parse markers,
evaluateGoalExit, stop on completed/deadlock/max-iterations/stalled/user-stop.
- routing: useBatchProcessor instantiates both runners and routes startBatchRun
on config.goalConfig; public signature unchanged. stop/kill already generic.
- tests: useGoalRunner.test.ts (8 tests) covering completion, stall, deadlock,
max-iterations, lifecycle, mid-run stop, template substitution, disabled gate.
Phase 03 of Goal-Driven Auto Run — the visible half of the feature. - GoalConfigPanel.tsx: Goal + Exit Criteria textareas and an iteration-limit control (Infinite/Limit segmented toggle + numeric input; Infinite => null, mirrors the loop infinite/max affordance). - BatchRunnerModal.tsx: Spec-Driven / Goal-Driven ToggleButtonGroup beneath the Playbook row with a contrast description. Goal mode swaps DocumentsPanel for GoalConfigPanel, keeps the worktree section, hides the fresh-context selector + agent prompt. handleGo branches to build a goalConfig BatchRunConfig (empty documents, no taskSelectionMode); Go gates on a non-empty goal; header task badge becomes a Goal-Driven pill. Layout cleanups: header retitled to 'Auto Run', fresh-context block stands solo above the prompt, its description moved above the toggle and bumped 10px -> text-xs. - Session: persist autoRunGoalConfig + autoRunDriveMode (named autoRunDriveMode, not autoRunMode, to avoid colliding with the existing edit/preview field) via updateSessionWith, debounced + flushed on launch/close; seeded on mount. - Tests: new Goal-Driven Mode suite (8 cases); tightened Go-button queries to exact 'Go' since the new Goal-Driven tab matched the /Go/ regex. Config flow verified intact onGo -> handleStartBatchRun -> startBatchRun -> startGoalRun (no field stripping, no documents.length gate). Full suite green (29,608 passed); lint, eslint, prettier clean.
…History) Phase 04 of Goal-Driven Auto Run. Every progress surface that spoke in "X of Y tasks" now renders the agent's self-reported goal percent, iteration, and rationale when goalMode is true; spec/document runs are unchanged. - AutoRunBottomPanel: optional goal prop -> "Goal: N%" (accent->success), "iteration N", and truncated rationale; AutoRun.tsx derives + passes it and tracks the goal fields in its memo comparator. - RightPanel + ThinkingStatusPill: drive the active-run bar/readout from goalProgress with a Goal label and rationale on hover. - History: per-iteration summary now "Goal progress: N% - <rationale>"; final entry keeps exit reason/detail + correct success flag. - Web bridge: thread goalMode/goalProgress/goalRationale/goalIteration through the broadcast serializer and all five AutoRunState type defs; AutoRunIndicator/AutoRunInline show goal percent + iteration. - Tests: goal-mode coverage for AutoRunBottomPanel and the mobile AutoRunIndicator; updated useGoalRunner final-summary helper.
…ase 05) - Record goal runs in stats behind a 'Goal: <80 chars>' documentPath via new shared goalRunLabel helper; surface a 'Goal' tag + percent in LongestAutoRunsTable - Add a Goal-Driven Mode section to AutoRunnerHelpModal (config inputs, progress marker, four exit conditions) - Harden parseGoalMarkers (tolerate trailing %, fenced/backtick markers, smart punctuation); add GOAL_RUN_HARD_ITERATION_CAP safety net for infinite runs; trim exitCriteria on launch - Add runner tests (marker-less -> stalled, oscillating -> hard cap), goalRunLabel unit tests, and an end-to-end integration test driving a goal run to completion and a stall through useBatchProcessor
📝 WalkthroughWalkthroughThis PR introduces Goal-Driven Auto Run mode alongside the existing spec-driven execution. Users can now define a free-text goal, exit criteria, and iteration limit, then watch as an AI agent iteratively makes progress toward that goal via structured HTML comment markers (progress percent, rationale, completion, deadlock). The system evaluates exit conditions in priority order (completed, deadlock, max-iterations, stalled) and tracks progress through a unified batch processing pipeline. ChangesGoal-Driven Auto Run Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR introduces Goal-Driven Auto Run mode — a document-less counterpart to the existing task/document loop where an agent pursues a free-text goal across repeated iterations, self-reports progress via
Confidence Score: 3/5The feature is well-architected and broadly safe to merge, but there is a data-flow bug in the deadlock path that will cause the agent's stated reason to be silently discarded and replaced with the wrong text in the History panel. The goal engine's deadlock handling has a disconnect between the prompt format and the evaluator: autorun-goal.md instructs agents to put the reason in the deadlock marker, but GoalIterationRecord has no deadlockReason field and useGoalRunner never stores markers.deadlockReason. The evaluator falls back to latest.rationale (the progress marker rationale), so the History detail will show the wrong text or 'no stated reason' every time a deadlock is declared with an explicit reason. This affects every user who triggers a deadlock in goal mode. The rest of the feature — stall detection, completion, max-iteration cap, UI, broadcast wiring, stats — is correct. src/renderer/hooks/batch/internal/useGoalRunner.ts and src/shared/goalDriven/types.ts need the deadlockReason field added and threaded through to the evaluator; src/shared/goalDriven/goalExitEvaluator.ts needs the deadlock branch updated to read from it. Important Files Changed
Sequence DiagramsequenceDiagram
participant Modal as BatchRunnerModal
participant Processor as useBatchProcessor
participant GoalRunner as useGoalRunner
participant Agent as onSpawnAgent
participant Parser as parseGoalMarkers
participant Evaluator as evaluateGoalExit
participant State as BatchRunState
Modal->>Processor: startBatchRun(config.goalConfig present)
Processor->>GoalRunner: startGoalRun(sessionId, config, folderPath)
GoalRunner->>State: dispatch START_BATCH (100 pseudo-tasks)
GoalRunner->>State: "updateBatch goalMode=true, goalProgress=0"
loop Each iteration
GoalRunner->>Agent: onSpawnAgent(sessionId, prompt)
Agent-->>GoalRunner: response with markers
GoalRunner->>Parser: parseGoalMarkers(response)
Parser-->>GoalRunner: "{progress, rationale, complete, deadlock, deadlockReason}"
GoalRunner->>State: update goalProgress, goalIteration, goalRationale
GoalRunner->>Evaluator: evaluateGoalExit(history, config)
Evaluator-->>GoalRunner: "{action: continue | stop, reason}"
alt "action == stop"
GoalRunner->>State: dispatch COMPLETE_BATCH
GoalRunner->>State: broadcastAutoRunState null
end
end
Reviews (1): Last reviewed commit: "MAESTRO: Stats, help docs, and hardening..." | Re-trigger Greptile |
| }; | ||
| const prompt = substituteTemplateVariables(goalPromptTemplate, templateContext); | ||
|
|
||
| const iterationStart = Date.now(); | ||
| let result: Awaited<ReturnType<SpawnAgentFn>>; | ||
| try { | ||
| result = await onSpawnAgent( | ||
| sessionId, | ||
| prompt, |
There was a problem hiding this comment.
Deadlock reason silently dropped from iteration record
markers.deadlockReason is parsed from <!-- maestro:deadlock: reason --> by parseGoalMarkers, but it is never stored in the GoalIterationRecord. The exit evaluator then falls back to latest.rationale?.trim() (the progress marker rationale) as the deadlock detail. When an agent follows the prompt exactly — placing the reason in the deadlock marker rather than in the progress rationale — the History panel shows either the wrong text or "Agent reported a deadlock with no stated reason."
The GoalIterationRecord interface needs a deadlockReason field, and the history.push call should set it from markers.deadlockReason. The evaluator's deadlock branch should then prefer that field over latest.rationale. The integration test at line 258 happens to work only because the test response embeds the reason in the progress rationale marker, which is the opposite of what the autorun-goal.md prompt instructs agents to do.
| - **Agent Path:** {{AGENT_PATH}} | ||
| - **Git Branch:** {{GIT_BRANCH}} | ||
| - **Auto Run Folder:** {{AUTORUN_FOLDER}} | ||
| - **Iteration:** {{LOOP_NUMBER}} |
There was a problem hiding this comment.
The
{{LOOP_NUMBER}} template variable is formatted as a 5-digit zero-padded string (e.g. "00003") via String(n).padStart(5, '0') in templateVariables.ts. That padding exists for file-ordering purposes in document runs and is confusing when shown to the agent as its human-readable iteration counter in goal mode.
| - **Iteration:** {{LOOP_NUMBER}} | |
| - **Iteration:** {{LOOP_NUMBER_HUMAN}} |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/__tests__/renderer/components/BatchRunnerModal.test.tsx (1)
1186-1191:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAvoid conditional assertions that let this test pass without validating close behavior.
At Line 1186,
header?.querySelector('button')is optional and wrapped inif (closeButton), so the test can pass even if the close button is missing. Make existence mandatory before clicking/asserting.✅ Suggested fix
- const closeButton = header?.querySelector('button'); - if (closeButton) { - fireEvent.click(closeButton); - expect(props.onClose).toHaveBeenCalled(); - } + const closeButton = header?.querySelector('button'); + expect(closeButton).not.toBeNull(); + fireEvent.click(closeButton!); + expect(props.onClose).toHaveBeenCalled();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/__tests__/renderer/components/BatchRunnerModal.test.tsx` around lines 1186 - 1191, The test currently uses an optional query (header?.querySelector('button')) and wraps the click/assert in an if, allowing the test to pass when the close button is missing; change it to require the button exists before interacting by querying/asserting it directly (e.g., use a non-optional lookup or expect(closeButton).toBeInTheDocument()) so you always fail when the close button is not rendered, then fireEvent.click(closeButton) and expect(props.onClose).toHaveBeenCalled(); keep references to screen.getByRole('heading', { name: "Auto Run" }), header, closeButton and props.onClose to locate the code to update.
🧹 Nitpick comments (3)
src/shared/goalDriven/types.ts (1)
49-60: 🏗️ Heavy liftPreserve
deadlockReasonin iteration history to avoid semantic loss.
GoalMarkerscarriesdeadlockReason, butGoalIterationRecorddrops it (Line 49 onward). That forces downstream logic to overloadrationalefor deadlock details, which can misreport the reason when both progress + deadlock markers appear in one iteration.Suggested contract change
export interface GoalIterationRecord { /** 1-based iteration number. */ iteration: number; /** Normalized progress for this iteration (0–100, never null in history). */ progress: number; /** Optional rationale captured from the iteration's progress marker. */ rationale: string | null; /** Whether the iteration reported completion. */ complete: boolean; /** Whether the iteration reported a deadlock. */ deadlock: boolean; + /** Optional deadlock reason captured from the deadlock marker. */ + deadlockReason: string | null; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/goalDriven/types.ts` around lines 49 - 60, The GoalIterationRecord interface currently omits deadlockReason while GoalMarkers includes it, causing loss of deadlock details; update the GoalIterationRecord type to include a deadlockReason field (string | null, optional if appropriate) and then propagate the deadlockReason from places that build GoalIterationRecord instances (look for code that maps from GoalMarkers to GoalIterationRecord) so iteration history preserves the deadlockReason instead of overloading rationale.src/shared/goalDriven/goalExitEvaluator.ts (1)
93-101: 🏗️ Heavy liftDeadlock detail should use a dedicated deadlock reason field, not
rationale.Line 94 currently reads deadlock reason from
latest.rationale. That conflates two semantics (progress rationale vs deadlock reason) and can produce misleading stop details.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/shared/goalDriven/goalExitEvaluator.ts` around lines 93 - 101, The code is reading deadlock details from latest.rationale which mixes semantics; change the deadlock handling in goalExitEvaluator (the block checking latest.deadlock) to read from a dedicated deadlock reason field (e.g., latest.deadlockReason or latest.deadlock.reason) instead of latest.rationale, and use that value for the returned detail string with the same fallback text ('Agent reported a deadlock with no stated reason.') so the stop.reason remains 'deadlock' but detail reflects the explicit deadlock reason field.src/main/ipc/handlers/web.ts (1)
274-279: ⚡ Quick winUse the shared
AutoRunStatetype instead of an inline payload shape.This handler’s inline type is already drifting from the other AutoRun state declarations, which weakens contract consistency across IPC boundaries.
♻️ Suggested refactor
import type { AITabData } from '../../web-server/services/broadcastService'; +import type { AutoRunState } from '../../web-server/types'; @@ - state: { - isRunning: boolean; - totalTasks: number; - completedTasks: number; - currentTaskIndex: number; - isStopping?: boolean; - // Multi-document progress fields - totalDocuments?: number; - currentDocumentIndex?: number; - totalTasksAcrossAllDocs?: number; - completedTasksAcrossAllDocs?: number; - // Goal-Driven mode fields - goalMode?: boolean; - goalProgress?: number; - goalRationale?: string; - goalIteration?: number; - } | null + state: AutoRunState | null🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/main/ipc/handlers/web.ts` around lines 274 - 279, The handler currently declares an inline AutoRun-like payload (the union type containing goalMode, goalProgress, goalRationale, goalIteration | null); replace that inline shape with the shared AutoRunState type by importing AutoRunState and using it for the payload parameter/field instead of the ad-hoc type, ensuring the handler signature and any runtime usages reference AutoRunState (and update any import statement to pull AutoRunState from its defining module).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/prompts/autorun-goal.md`:
- Around line 74-99: Update the three fenced code block examples that show
HTML-like markers to include a language tag (change ``` to ```html) for the
markers <!-- maestro:progress N | one-line rationale -->, <!-- maestro:progress
100 | goal achieved: <what was accomplished> --> <!-- maestro:goal-complete -->,
and <!-- maestro:deadlock: brief reason you cannot proceed --> to satisfy MD040,
and remove the internal trailing space in the inline code span `MAESTRO: `
(change to `MAESTRO:`) referenced in the Version control item so it no longer
triggers MD038; ensure the exact marker strings remain unchanged except for
code-fence language and the inline token spacing.
In `@src/renderer/components/AutoRun/AutoRun.tsx`:
- Around line 542-557: The goalInfo useMemo currently builds goal state whenever
goalMode is set even if the run stopped; modify the guard to require
batchRunState?.isRunning (e.g. change the ternary to batchRunState?.goalMode &&
batchRunState?.isRunning ? ...) and add batchRunState?.isRunning to the useMemo
dependency array so goalInfo only exists during active runs; apply the same
change to the other similar goal-derived slice elsewhere in this file (the other
goalInfo-like computation).
In `@src/renderer/components/AutoRun/AutoRunBottomPanel.tsx`:
- Around line 105-115: In AutoRunBottomPanel, normalize goal.progress into a
clamped value (e.g., const clampedProgress = Math.max(0, Math.min(100,
goal.progress || 0))) and use clampedProgress everywhere in this component
instead of goal.progress — update the color decision (goal.progress >= 100) and
the displayed text "Goal: {goal.progress}%" to use clampedProgress so the UI
consistently shows a 0–100% value and color logic matches the clamped state;
reference the AutoRunBottomPanel component and the span rendering the goal
percent.
In `@src/renderer/components/ThinkingStatusPill.tsx`:
- Around line 248-273: The expanded AutoRun dropdown still always renders "x/y
tasks" and should match the primary pill's goal-mode display; update the
rendering logic inside the expanded AutoRun row (the block that currently uses
completedTasks/totalTasks and the hardcoded "tasks" label) to conditionally show
goal UI when autoRunState.goalMode is true — display "Goal:" with
autoRunState.goalProgress (default 0%) and optional "· iteration
{autoRunState.goalIteration}" and use autoRunState.goalRationale as title,
otherwise keep the existing "Tasks:" + completedTasks/totalTasks rendering;
mirror the same classNames/styles used in the existing goal-mode block so
visuals stay consistent.
In `@src/renderer/hooks/batch/batchReducer.ts`:
- Around line 374-379: The reducer currently only spreads goal fields when
payload.goalRationale/goalExitReason !== undefined, preventing clearing them;
update the spread conditions to check for key presence (e.g.,
Object.prototype.hasOwnProperty.call(payload, 'goalRationale') and
'goalExitReason') so the reducer will apply explicit null/empty values to clear
stale metadata, and ensure the action creator/payload construction only omits
keys when truly not intended (i.e., include keys with null/'' when you want them
cleared).
In `@src/renderer/hooks/batch/internal/useBatchBroadcast.ts`:
- Around line 60-66: The broadcast payload for goal-driven state is missing
goalExitReason, so update the object created in useBatchBroadcast (the payload
that currently includes goalMode, goalProgress, goalRationale, goalIteration) to
also include goalExitReason; locate where the broadcast is assembled in
useBatchBroadcast.ts and add goalExitReason: state.goalExitReason to the same
payload so downstream web/mobile clients receive the exit reason.
In `@src/renderer/hooks/batch/internal/useGoalRunner.ts`:
- Around line 568-570: The catch block in useGoalRunner around the
onAddHistoryEntry call should not silently swallow errors; import and call the
Sentry utilities (captureException or captureMessage from src/utils/sentry.ts)
inside that catch to report the failure with context (include function name
"onAddHistoryEntry", the affected goal id/metadata and any relevant variables),
and only suppress the error after logging if it is known/recoverable; update
useGoalRunner to report unexpected exceptions via captureException and add a
brief contextual message via captureMessage when appropriate.
In `@src/web/mobile/AutoRunInline.tsx`:
- Around line 732-736: The empty-document goal-run branch currently hides the
goal banner when isErrorPaused is true, removing Resume/Skip/Abort controls;
update the conditional around AutoRunIndicator (referenced as AutoRunIndicator
and the surrounding checks isErrorPaused, isRunning, autoRunState?.goalMode) so
that when autoRunState?.goalMode is true you still render the banner or a
recovery banner even if isErrorPaused is true — e.g., change the guard to render
AutoRunIndicator (or an error-recovery variant) when goalMode is set and either
running or error-paused, and ensure the rendered component receives the recovery
handler props (onResume/onSkip/onAbort) when those handlers are present so the
controls remain visible during error pause.
---
Outside diff comments:
In `@src/__tests__/renderer/components/BatchRunnerModal.test.tsx`:
- Around line 1186-1191: The test currently uses an optional query
(header?.querySelector('button')) and wraps the click/assert in an if, allowing
the test to pass when the close button is missing; change it to require the
button exists before interacting by querying/asserting it directly (e.g., use a
non-optional lookup or expect(closeButton).toBeInTheDocument()) so you always
fail when the close button is not rendered, then fireEvent.click(closeButton)
and expect(props.onClose).toHaveBeenCalled(); keep references to
screen.getByRole('heading', { name: "Auto Run" }), header, closeButton and
props.onClose to locate the code to update.
---
Nitpick comments:
In `@src/main/ipc/handlers/web.ts`:
- Around line 274-279: The handler currently declares an inline AutoRun-like
payload (the union type containing goalMode, goalProgress, goalRationale,
goalIteration | null); replace that inline shape with the shared AutoRunState
type by importing AutoRunState and using it for the payload parameter/field
instead of the ad-hoc type, ensuring the handler signature and any runtime
usages reference AutoRunState (and update any import statement to pull
AutoRunState from its defining module).
In `@src/shared/goalDriven/goalExitEvaluator.ts`:
- Around line 93-101: The code is reading deadlock details from latest.rationale
which mixes semantics; change the deadlock handling in goalExitEvaluator (the
block checking latest.deadlock) to read from a dedicated deadlock reason field
(e.g., latest.deadlockReason or latest.deadlock.reason) instead of
latest.rationale, and use that value for the returned detail string with the
same fallback text ('Agent reported a deadlock with no stated reason.') so the
stop.reason remains 'deadlock' but detail reflects the explicit deadlock reason
field.
In `@src/shared/goalDriven/types.ts`:
- Around line 49-60: The GoalIterationRecord interface currently omits
deadlockReason while GoalMarkers includes it, causing loss of deadlock details;
update the GoalIterationRecord type to include a deadlockReason field (string |
null, optional if appropriate) and then propagate the deadlockReason from places
that build GoalIterationRecord instances (look for code that maps from
GoalMarkers to GoalIterationRecord) so iteration history preserves the
deadlockReason instead of overloading rationale.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e6d3248-c082-443a-bc12-8b1d3134366e
📒 Files selected for processing (36)
src/__tests__/integration/GoalDrivenAutoRun.test.tsxsrc/__tests__/renderer/components/AutoRun/AutoRunBottomPanel.test.tsxsrc/__tests__/renderer/components/BatchRunnerModal.test.tsxsrc/__tests__/renderer/hooks/batch/useGoalRunner.test.tssrc/__tests__/shared/goalDriven/goalExitEvaluator.test.tssrc/__tests__/shared/goalDriven/goalMarkers.test.tssrc/__tests__/shared/goalDriven/goalRun.simulation.test.tssrc/__tests__/shared/goalDriven/goalRunLabel.test.tssrc/__tests__/web/mobile/AutoRunIndicator.test.tsxsrc/main/ipc/handlers/web.tssrc/main/preload/web.tssrc/main/web-server/types.tssrc/prompts/autorun-goal.mdsrc/renderer/components/AutoRun/AutoRun.tsxsrc/renderer/components/AutoRun/AutoRunBottomPanel.tsxsrc/renderer/components/AutoRun/AutoRunnerHelpModal.tsxsrc/renderer/components/BatchRunnerModal.tsxsrc/renderer/components/GoalConfigPanel.tsxsrc/renderer/components/RightPanel.tsxsrc/renderer/components/ThinkingStatusPill.tsxsrc/renderer/components/UsageDashboard/LongestAutoRunsTable.tsxsrc/renderer/global.d.tssrc/renderer/hooks/batch/batchReducer.tssrc/renderer/hooks/batch/internal/useBatchBroadcast.tssrc/renderer/hooks/batch/internal/useGoalRunner.tssrc/renderer/hooks/batch/useBatchProcessor.tssrc/renderer/types/index.tssrc/shared/goalDriven/goalExitEvaluator.tssrc/shared/goalDriven/goalMarkers.tssrc/shared/goalDriven/goalRunLabel.tssrc/shared/goalDriven/types.tssrc/shared/promptDefinitions.tssrc/shared/templateVariables.tssrc/web/hooks/useWebSocket.tssrc/web/mobile/AutoRunIndicator.tsxsrc/web/mobile/AutoRunInline.tsx
| ``` | ||
| <!-- maestro:progress N | one-line rationale --> | ||
| ``` | ||
|
|
||
| - `N` is your honest **0–100** self-assessment of how far the work has come toward the goal and its exit criteria above. Be conservative and grounded — base it on what is actually built and verified, not on how much you intend to do. | ||
| - The `| one-line rationale` is a short human-readable note describing where things stand (e.g. `data layer migrated, UI still pending`). It is optional but strongly encouraged — it shows up in the progress UI. | ||
| - This marker is how the engine drives the progress bar and decides whether to run another iteration. **A response with no progress marker is treated as zero progress** and counts toward a stall. | ||
|
|
||
| 5. **Declare completion only when genuinely done.** When the goal is fully achieved and the exit criteria are satisfied, end your response with both a 100 progress marker and the completion marker: | ||
|
|
||
| ``` | ||
| <!-- maestro:progress 100 | goal achieved: <what was accomplished> --> | ||
| <!-- maestro:goal-complete --> | ||
| ``` | ||
|
|
||
| A progress of `100` on its own is also treated as completion, but emit the explicit `goal-complete` marker when you are certain. Do not declare completion prematurely — if work or verification remains, report a lower number and keep going. | ||
|
|
||
| 6. **Declare a deadlock only for a true blocker.** If you hit something that genuinely prevents any further progress toward the goal — a missing dependency or credential you cannot obtain, a contradiction in the goal itself, a destructive action you refuse to take, or a hard external blocker — stop and end your response with: | ||
|
|
||
| ``` | ||
| <!-- maestro:deadlock: brief reason you cannot proceed --> | ||
| ``` | ||
|
|
||
| The reason text is shown in the History panel. Reserve this for real dead-ends; do NOT use it for ordinary setbacks you can work around on the next iteration. When in doubt, report partial progress and continue instead of declaring a deadlock. | ||
|
|
||
| 7. **Version control.** For any code or documentation changes, if we're in a GitHub repo: commit using a descriptive message prefixed with `MAESTRO: `, and push. Update CLAUDE.md / AGENTS.md / README.md when appropriate. |
There was a problem hiding this comment.
Resolve markdownlint warnings in marker examples and code span formatting.
Line 74, Line 84, and Line 93 use fenced code blocks without language tags (MD040).
Line 99 has a code span with internal spacing (MAESTRO: ) triggering MD038.
Lint-safe markdown diff
- ```
+ ```html
<!-- maestro:progress N | one-line rationale -->
```
- ```
+ ```html
<!-- maestro:progress 100 | goal achieved: <what was accomplished> -->
<!-- maestro:goal-complete -->
```
- ```
+ ```html
<!-- maestro:deadlock: brief reason you cannot proceed -->
```
-7. **Version control.** For any code or documentation changes, if we're in a GitHub repo: commit using a descriptive message prefixed with `MAESTRO: `, and push. Update CLAUDE.md / AGENTS.md / README.md when appropriate.
+7. **Version control.** For any code or documentation changes, if we're in a GitHub repo: commit using a descriptive message prefixed with `MAESTRO:`, and push. Update CLAUDE.md / AGENTS.md / README.md when appropriate.🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 74-74: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 84-84: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 93-93: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 99-99: Spaces inside code span elements
(MD038, no-space-in-code)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/prompts/autorun-goal.md` around lines 74 - 99, Update the three fenced
code block examples that show HTML-like markers to include a language tag
(change ``` to ```html) for the markers <!-- maestro:progress N | one-line
rationale -->, <!-- maestro:progress 100 | goal achieved: <what was
accomplished> --> <!-- maestro:goal-complete -->, and <!-- maestro:deadlock:
brief reason you cannot proceed --> to satisfy MD040, and remove the internal
trailing space in the inline code span `MAESTRO: ` (change to `MAESTRO:`)
referenced in the Version control item so it no longer triggers MD038; ensure
the exact marker strings remain unchanged except for code-fence language and the
inline token spacing.
| const goalInfo = useMemo( | ||
| () => | ||
| batchRunState?.goalMode | ||
| ? { | ||
| progress: batchRunState.goalProgress ?? 0, | ||
| iteration: batchRunState.goalIteration ?? 0, | ||
| rationale: batchRunState.goalRationale, | ||
| } | ||
| : null, | ||
| [ | ||
| batchRunState?.goalMode, | ||
| batchRunState?.goalProgress, | ||
| batchRunState?.goalIteration, | ||
| batchRunState?.goalRationale, | ||
| ] | ||
| ); |
There was a problem hiding this comment.
Gate goal panel state to active runs only.
goalInfo is derived whenever goal mode is set, even if the run is no longer active. That can keep the bottom panel in goal display mode after stop. Use isRunning as part of the guard so this slice is only present during an active goal run.
💡 Suggested patch
const goalInfo = useMemo(
() =>
- batchRunState?.goalMode
+ batchRunState?.isRunning && batchRunState?.goalMode
? {
progress: batchRunState.goalProgress ?? 0,
iteration: batchRunState.goalIteration ?? 0,
rationale: batchRunState.goalRationale,
}
: null,
[
+ batchRunState?.isRunning,
batchRunState?.goalMode,
batchRunState?.goalProgress,
batchRunState?.goalIteration,
batchRunState?.goalRationale,
]
);Also applies to: 849-850
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/components/AutoRun/AutoRun.tsx` around lines 542 - 557, The
goalInfo useMemo currently builds goal state whenever goalMode is set even if
the run stopped; modify the guard to require batchRunState?.isRunning (e.g.
change the ternary to batchRunState?.goalMode && batchRunState?.isRunning ? ...)
and add batchRunState?.isRunning to the useMemo dependency array so goalInfo
only exists during active runs; apply the same change to the other similar
goal-derived slice elsewhere in this file (the other goalInfo-like computation).
| {goal ? ( | ||
| <span className="flex items-center gap-2 min-w-0" style={{ color: theme.colors.textDim }}> | ||
| {/* Goal percent — accent until complete, then success (mirrors task readout) */} | ||
| <span | ||
| className="shrink-0 font-medium" | ||
| style={{ | ||
| color: | ||
| taskCounts.completed === taskCounts.total | ||
| ? theme.colors.success | ||
| : theme.colors.accent, | ||
| color: goal.progress >= 100 ? theme.colors.success : theme.colors.accent, | ||
| }} | ||
| > | ||
| {taskCounts.completed} | ||
| </span>{' '} | ||
| of <span style={{ color: theme.colors.accent }}>{taskCounts.total}</span> task | ||
| {taskCounts.total !== 1 ? 's' : ''} | ||
| {!isCompact && ' completed'} | ||
| Goal: {goal.progress}% | ||
| </span> |
There was a problem hiding this comment.
Normalize goal percent before rendering.
goal.progress is rendered directly here, while other goal progress surfaces clamp it. Clamp once in this component to keep display/state semantics consistent and avoid out-of-range values in UI.
💡 Suggested patch
export const AutoRunBottomPanel = memo(function AutoRunBottomPanel({
@@
}: AutoRunBottomPanelProps) {
const bottomPanelRef = useRef<HTMLDivElement>(null);
const [isCompact, setIsCompact] = useState(false);
+ const normalizedGoalProgress =
+ goal == null ? null : Math.min(100, Math.max(0, Math.round(goal.progress)));
@@
{goal ? (
@@
<span
className="shrink-0 font-medium"
style={{
- color: goal.progress >= 100 ? theme.colors.success : theme.colors.accent,
+ color:
+ normalizedGoalProgress !== null && normalizedGoalProgress >= 100
+ ? theme.colors.success
+ : theme.colors.accent,
}}
>
- Goal: {goal.progress}%
+ Goal: {normalizedGoalProgress}%
</span>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/components/AutoRun/AutoRunBottomPanel.tsx` around lines 105 -
115, In AutoRunBottomPanel, normalize goal.progress into a clamped value (e.g.,
const clampedProgress = Math.max(0, Math.min(100, goal.progress || 0))) and use
clampedProgress everywhere in this component instead of goal.progress — update
the color decision (goal.progress >= 100) and the displayed text "Goal:
{goal.progress}%" to use clampedProgress so the UI consistently shows a 0–100%
value and color logic matches the clamped state; reference the
AutoRunBottomPanel component and the span rendering the goal percent.
| {/* Progress — goal percent for goal runs, task count otherwise */} | ||
| {autoRunState.goalMode ? ( | ||
| <div | ||
| className="flex items-center gap-1 shrink-0 text-xs" | ||
| style={{ color: theme.colors.textDim }} | ||
| title={autoRunState.goalRationale || undefined} | ||
| > | ||
| <span>Goal:</span> | ||
| <span className="font-medium" style={{ color: theme.colors.textMain }}> | ||
| {autoRunState.goalProgress ?? 0}% | ||
| </span> | ||
| {autoRunState.goalIteration ? ( | ||
| <span className="opacity-70">· iteration {autoRunState.goalIteration}</span> | ||
| ) : null} | ||
| </div> | ||
| ) : ( | ||
| <div | ||
| className="flex items-center gap-1 shrink-0 text-xs" | ||
| style={{ color: theme.colors.textDim }} | ||
| > | ||
| <span>Tasks:</span> | ||
| <span className="font-medium" style={{ color: theme.colors.textMain }}> | ||
| {completedTasks}/{totalTasks} | ||
| </span> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Keep the expanded AutoRun dropdown consistent in goal mode.
The primary pill now shows goal progress, but the expanded AutoRun row still hardcodes task wording at Line 383 (x/y tasks). In goal runs this becomes inconsistent/misleading.
💡 Suggested patch
<div
className="flex items-center justify-between gap-3 w-full px-3 py-2"
style={{ color: theme.colors.textMain }}
>
@@
- <span className="text-xs" style={{ color: theme.colors.textDim }}>
- {completedTasks}/{totalTasks} tasks
- </span>
+ <span className="text-xs" style={{ color: theme.colors.textDim }}>
+ {autoRunState.goalMode
+ ? `Goal ${Math.min(100, Math.max(0, autoRunState.goalProgress ?? 0))}%${
+ autoRunState.goalIteration
+ ? ` · iteration ${autoRunState.goalIteration}`
+ : ''
+ }`
+ : `${completedTasks}/${totalTasks} tasks`}
+ </span>
</div>Also applies to: 371-385
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/components/ThinkingStatusPill.tsx` around lines 248 - 273, The
expanded AutoRun dropdown still always renders "x/y tasks" and should match the
primary pill's goal-mode display; update the rendering logic inside the expanded
AutoRun row (the block that currently uses completedTasks/totalTasks and the
hardcoded "tasks" label) to conditionally show goal UI when
autoRunState.goalMode is true — display "Goal:" with autoRunState.goalProgress
(default 0%) and optional "· iteration {autoRunState.goalIteration}" and use
autoRunState.goalRationale as title, otherwise keep the existing "Tasks:" +
completedTasks/totalTasks rendering; mirror the same classNames/styles used in
the existing goal-mode block so visuals stay consistent.
| // Goal-Driven mode fields (only set by the goal runner) | ||
| ...(payload.goalMode !== undefined && { goalMode: payload.goalMode }), | ||
| ...(payload.goalProgress !== undefined && { goalProgress: payload.goalProgress }), | ||
| ...(payload.goalRationale !== undefined && { goalRationale: payload.goalRationale }), | ||
| ...(payload.goalIteration !== undefined && { goalIteration: payload.goalIteration }), | ||
| ...(payload.goalExitReason !== undefined && { goalExitReason: payload.goalExitReason }), |
There was a problem hiding this comment.
Goal optional fields can’t be cleared once set.
Line 377/379 only applies updates when value is not undefined, so goalRationale/goalExitReason cannot be reset to empty/absent state via UPDATE_PROGRESS. This can leave stale goal metadata in store state.
Suggested direction
- ...(payload.goalRationale !== undefined && { goalRationale: payload.goalRationale }),
- ...(payload.goalExitReason !== undefined && { goalExitReason: payload.goalExitReason }),
+ ...(Object.prototype.hasOwnProperty.call(payload, 'goalRationale') && {
+ goalRationale: payload.goalRationale,
+ }),
+ ...(Object.prototype.hasOwnProperty.call(payload, 'goalExitReason') && {
+ goalExitReason: payload.goalExitReason,
+ }),(And ensure payload construction only includes those keys when intended.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/hooks/batch/batchReducer.ts` around lines 374 - 379, The reducer
currently only spreads goal fields when payload.goalRationale/goalExitReason !==
undefined, preventing clearing them; update the spread conditions to check for
key presence (e.g., Object.prototype.hasOwnProperty.call(payload,
'goalRationale') and 'goalExitReason') so the reducer will apply explicit
null/empty values to clear stale metadata, and ensure the action creator/payload
construction only omits keys when truly not intended (i.e., include keys with
null/'' when you want them cleared).
| // Goal-Driven mode — web/mobile render goal percent + iteration in | ||
| // place of task counts when goalMode is true. | ||
| goalMode: state.goalMode, | ||
| goalProgress: state.goalProgress, | ||
| goalRationale: state.goalRationale, | ||
| goalIteration: state.goalIteration, | ||
| }); |
There was a problem hiding this comment.
goalExitReason is missing from the web/mobile broadcast contract.
The goal state broadcast includes progress fields but omits goalExitReason, so downstream clients can’t reliably render why a goal run ended.
Minimal fix
goalMode: state.goalMode,
goalProgress: state.goalProgress,
goalRationale: state.goalRationale,
goalIteration: state.goalIteration,
+ goalExitReason: state.goalExitReason,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Goal-Driven mode — web/mobile render goal percent + iteration in | |
| // place of task counts when goalMode is true. | |
| goalMode: state.goalMode, | |
| goalProgress: state.goalProgress, | |
| goalRationale: state.goalRationale, | |
| goalIteration: state.goalIteration, | |
| }); | |
| // Goal-Driven mode — web/mobile render goal percent + iteration in | |
| // place of task counts when goalMode is true. | |
| goalMode: state.goalMode, | |
| goalProgress: state.goalProgress, | |
| goalRationale: state.goalRationale, | |
| goalIteration: state.goalIteration, | |
| goalExitReason: state.goalExitReason, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/hooks/batch/internal/useBatchBroadcast.ts` around lines 60 - 66,
The broadcast payload for goal-driven state is missing goalExitReason, so update
the object created in useBatchBroadcast (the payload that currently includes
goalMode, goalProgress, goalRationale, goalIteration) to also include
goalExitReason; locate where the broadcast is assembled in useBatchBroadcast.ts
and add goalExitReason: state.goalExitReason to the same payload so downstream
web/mobile clients receive the exit reason.
| } catch { | ||
| // Ignore history errors. | ||
| } |
There was a problem hiding this comment.
Do not silently swallow final history-write errors.
Line 568 catches and ignores unexpected failures from onAddHistoryEntry, which hides production issues. Capture with Sentry context (and only swallow if explicitly expected/recoverable).
As per coding guidelines: “Do not silently swallow errors… Use Sentry utilities (captureException, captureMessage) from src/utils/sentry.ts for explicit error reporting with context.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/renderer/hooks/batch/internal/useGoalRunner.ts` around lines 568 - 570,
The catch block in useGoalRunner around the onAddHistoryEntry call should not
silently swallow errors; import and call the Sentry utilities (captureException
or captureMessage from src/utils/sentry.ts) inside that catch to report the
failure with context (include function name "onAddHistoryEntry", the affected
goal id/metadata and any relevant variables), and only suppress the error after
logging if it is known/recoverable; update useGoalRunner to report unexpected
exceptions via captureException and add a brief contextual message via
captureMessage when appropriate.
| {/* Goal runs need not have any documents — surface live goal progress | ||
| even in the empty-document state (reuses AutoRunIndicator). */} | ||
| {!isErrorPaused && isRunning && autoRunState?.goalMode && ( | ||
| <AutoRunIndicator state={autoRunState} /> | ||
| )} |
There was a problem hiding this comment.
Render error-recovery actions in empty-document goal runs.
When isErrorPaused is true in the empty-doc branch, the goal banner is hidden but no recovery banner is shown, so Resume/Skip/Abort controls disappear even if handlers are provided.
💡 Suggested fix
- {!isErrorPaused && isRunning && autoRunState?.goalMode && (
- <AutoRunIndicator state={autoRunState} />
- )}
+ {isErrorPaused && (onResumeAfterError || onSkipAfterError || onAbortAfterError) && (
+ <AutoRunIndicator
+ state={autoRunState}
+ onResume={onResumeAfterError}
+ onSkipDocument={onSkipAfterError}
+ onAbort={onAbortAfterError}
+ />
+ )}
+ {!isErrorPaused && isRunning && autoRunState?.goalMode && (
+ <AutoRunIndicator state={autoRunState} />
+ )}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/web/mobile/AutoRunInline.tsx` around lines 732 - 736, The empty-document
goal-run branch currently hides the goal banner when isErrorPaused is true,
removing Resume/Skip/Abort controls; update the conditional around
AutoRunIndicator (referenced as AutoRunIndicator and the surrounding checks
isErrorPaused, isRunning, autoRunState?.goalMode) so that when
autoRunState?.goalMode is true you still render the banner or a recovery banner
even if isErrorPaused is true — e.g., change the guard to render
AutoRunIndicator (or an error-recovery variant) when goalMode is set and either
running or error-paused, and ensure the rendered component receives the recovery
handler props (onResume/onSkip/onAbort) when those handlers are present so the
controls remain visible during error pause.
Auto Run Summary
Documents processed:
Total tasks completed: 34
Changes
CHANGES
CHANGES
CHANGES
CHANGES
CHANGES - Added CLI discovery watchdog to republish missing/stale server info file 🔭 - Watchdog auto-starts at app launch for stronger maestro-cli reliability 🛡️ - Cleanly stops watchdog on quit to avoid post-exit file rewrites 🧹 - Watchdog can bootstrap the CLI server when none is active 🔌 - Ungrouped Agents header now hides when empty for cleaner session list 🧩 - New compact ungroup drop-zone appears when all sessions are grouped 🎯 - “New Group” button stays visible even without ungrouped sessions ➕ - Removed Quick Actions “Edit Prompt” entries for a slimmer command list ✂️ - Added comprehensive tests covering watchdog rewrite, noop, startup, and stop ✅
CHANGES
This PR was automatically created by Maestro Auto Run.
Summary by CodeRabbit
New Features
Tests
Documentation