feat(mastracode): add /goal slash command for persistent cross-turn goals (Ralph loop) - #16065
Conversation
…oals (Ralph loop) Implements the /goal command inspired by Hermes Agent and OpenAI Codex ralph-loop patterns. After each agent turn, a lightweight judge LLM evaluates whether the stated objective has been achieved. If not, a continuation prompt is automatically sent to keep the agent working. Features: - /goal <text>: Set a standing goal and kick off first turn - /goal status: Show current goal state and turn usage - /goal pause: Pause the continuation loop - /goal resume: Resume goal (resets turn counter) - /goal clear: Drop the goal entirely - Ctrl+C during goal loop pauses the goal - User messages in the queue preempt the goal loop - Status line shows active goal turn counter - Turn budget (default 20) prevents runaway loops - Judge fails OPEN (defaults to continue on error) Co-Authored-By: tyler <tylerdbarnes@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: d6ddc95 The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a persistent cross-turn goal system: new GoalManager with judge-model evaluation, a Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 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 unit tests (beta)
Comment |
|
Thank you for your contribution! Please ensure that your PR fixes an existing issue and that you have linked it in the description (e.g. with We use CodeRabbit for automated code reviews. Please address all feedback from CodeRabbit by either making changes to your PR or leaving a comment explaining why you disagree with the feedback. Since CodeRabbit is an AI, it may occasionally provide incorrect feedback. Addressing CodeRabbit's feedback will greatly increase the chances of your PR being merged. We appreciate your understanding and cooperation in helping us maintain high code quality standards. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mastracode/src/tui/commands/goal.ts`:
- Around line 57-62: Wrap the kickoff sequence in try/catch so failures from
state.harness.createThread() and state.harness.sendMessage(...) are not
swallowed: call createThread inside a try and if it fails set
state.pendingNewThread = true and surface an error to the user (log and
return/fail the command), then call sendMessage inside its own try and on
failure surface a user-visible error and optionally pause the goal instead of
returning success; update the same pattern for the duplicate block that handles
lines around state.pendingNewThread (the second path at ~78-83) and reference
the goal.objective in the error message so users know which goal failed to
continue.
In `@mastracode/src/tui/goal-manager.ts`:
- Around line 121-127: The budget check currently increments this.goal.turnsUsed
then immediately enforces maxTurns causing the latest assistant response to
never be judged; update the logic in goal-manager (the code around
this.goal.turnsUsed++, the `"continue"` decision and the block that sets
this.goal.status = 'paused') so that you increment turnsUsed when a new turn
starts but only evaluate this.goal.turnsUsed >= this.goal.maxTurns after
handling the assistant's response/judgement and after a `"continue"` decision;
i.e., allow the current turn to be judged and potentially mark the goal 'done'
before setting status to 'paused' when the post-judgement count meets or exceeds
maxTurns.
In `@mastracode/src/tui/handlers/agent-lifecycle.ts`:
- Around line 113-117: The current handler only pauses goals when
state.userInitiatedAbort is set after agent_aborted; update the idle
Ctrl+C/SIGINT handlers to also check and pause the goal manager so Ctrl+C pauses
goals during the inter-turn idle window. Concretely, in the places that handle
the idle/clear-input SIGINT (the code path that currently clears input on
Ctrl+C), add logic to check state.goalManager.isActive() and call
state.goalManager.pause(), then call showInfo(state, 'Goal paused (interrupted).
Use /goal resume to continue.') and avoid the normal clear-input fallback when a
goal was paused; keep the existing userInitiatedAbort branch unchanged.
- Around line 177-199: After evaluateAfterTurn(state) resolves, re-check the
queue and current goal state before auto-continuing: inside the
.then(continuation => { ... }) block (the code that currently calls
ctx.fireMessage(continuation) and showInfo), first verify that
state.pendingQueuedActions is empty and that state.goalManager.getGoal() still
exists and is not paused/cleared/done; only call showInfo(...Continuing...) and
ctx.fireMessage(continuation) if those conditions hold. If pendingQueuedActions
is non-empty or the goal status changed (paused/cleared/done), skip the
auto-continuation and respect the user queue or new goal state. Keep the
existing .catch behavior for errors from evaluateAfterTurn().
🪄 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: 2c2f7567-0b94-44d6-9a4e-e4d181e7c09d
📒 Files selected for processing (9)
mastracode/src/tui/command-dispatch.tsmastracode/src/tui/commands/goal.tsmastracode/src/tui/commands/index.tsmastracode/src/tui/components/help-overlay.tsmastracode/src/tui/goal-manager.tsmastracode/src/tui/handlers/agent-lifecycle.tsmastracode/src/tui/setup.tsmastracode/src/tui/state.tsmastracode/src/tui/status-line.ts
…hread metadata - Replace hardcoded model cascade with interactive model picker - Preselect to last judge choice (saved in settings) or main model - Store goal state in thread metadata so it survives thread switches - Load goal from thread metadata on thread_changed event - Fix prettier formatting issues from CI Co-Authored-By: tyler <tylerdbarnes@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mastracode/src/tui/event-dispatch.ts`:
- Around line 145-146: In the thread_created branch of the event handler, call
state.goalManager.loadFromThreadMetadata with event.thread.metadata (same as
used in thread_changed) so the newly created thread hydrates its goal state; if
event.thread.metadata is undefined or null, explicitly clear the in-memory goal
(e.g., via a clear/ reset method or by calling loadFromThreadMetadata with
undefined) to avoid carrying over the previous thread’s goal—update the
thread_created branch to mirror the thread_changed behavior by invoking
state.goalManager.loadFromThreadMetadata(event.thread.metadata as Record<string,
unknown> | undefined) or clearing the goal when metadata is absent.
In `@mastracode/src/tui/goal-manager.ts`:
- Around line 254-257: The buildContinuationPrompt currently injects free-form
judgeReason into the system prompt, which can let the LLM steer future turns and
lose the original objective; change buildContinuationPrompt to produce a fixed
continuation prompt that explicitly restates the goal objective (use
this.goal.objective) and the turn counts (this.goal.turnsUsed /
this.goal.maxTurns) and do NOT embed the raw judgeReason string; if you need to
surface the judge outcome, map judgeReason to a short, known enum or omit it and
instead append a sanitized fixed note like "Judge: proceed" or "Judge: revise"
rather than the free-form text.
In `@mastracode/src/tui/handlers/agent-lifecycle.ts`:
- Around line 113-117: When handling Ctrl+C you pause the in-memory goal loop
(state.goalManager.pause()) but do not persist that paused status, so after a
thread switch/restart the goal will be restored as active; update the abort
branch to also set and persist a "paused" flag on the current thread's metadata
(using your thread metadata API on state, e.g. add something like
currentThread.metadata.paused = true and call the existing persistence method -
match your project API) immediately after calling state.goalManager.pause(), and
keep the showInfo(state, ...) call; also ensure the restore logic reads this
metadata flag to keep the goal paused on resume.
🪄 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: 7b67f6c1-5aa1-48b4-92be-c817eed75281
📒 Files selected for processing (4)
mastracode/src/tui/commands/goal.tsmastracode/src/tui/event-dispatch.tsmastracode/src/tui/goal-manager.tsmastracode/src/tui/handlers/agent-lifecycle.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- mastracode/src/tui/commands/goal.ts
- Judge current turn before enforcing budget (allows last turn to be marked done) - Add try/catch for sendMessage failures (pause goal with error message) - Clear/load goal state on thread_created (not just thread_changed) - Idle Ctrl+C (empty input) pauses active goal Co-Authored-By: tyler <tylerdbarnes@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mastracode/src/tui/commands/goal.ts`:
- Around line 97-98: Guard against missing settings.models by using optional
chaining and nullish coalescing when reading and writing the judge model: change
the access that computes lastJudgeModelId (currently using (settings.models as
Record<string, unknown>).goalJudgeModel) to safely read
settings.models?.goalJudgeModel as string | undefined and compute preselectedId
with the nullish fallback as now; likewise update the save path that writes into
settings.models to ensure settings.models is initialized first (e.g., ensure
settings.models = settings.models ?? {} before assigning goalJudgeModel) so
reads/writes of goalJudgeModel cannot throw; adjust references in this file to
use lastJudgeModelId and preselectedId as before.
🪄 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: 38f57479-64ed-4a49-9e02-bc78db54ea84
📒 Files selected for processing (4)
mastracode/src/tui/commands/goal.tsmastracode/src/tui/event-dispatch.tsmastracode/src/tui/goal-manager.tsmastracode/src/tui/setup.ts
✅ Files skipped from review due to trivial changes (1)
- mastracode/src/tui/goal-manager.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- mastracode/src/tui/setup.ts
| const lastJudgeModelId = (settings.models as Record<string, unknown>).goalJudgeModel as string | undefined; | ||
| const preselectedId = lastJudgeModelId ?? state.harness.getCurrentModelId() ?? undefined; |
There was a problem hiding this comment.
Guard against missing settings.models property.
If loadSettings() returns a settings object without a models property (e.g., fresh install, corrupted settings), accessing (settings.models as Record<string, unknown>).goalJudgeModel will throw a TypeError. The same issue exists on line 112 when saving.
Proposed fix using optional chaining and nullish coalescing
const settings = loadSettings();
- const lastJudgeModelId = (settings.models as Record<string, unknown>).goalJudgeModel as string | undefined;
+ const lastJudgeModelId = (settings.models as Record<string, unknown> | undefined)?.goalJudgeModel as string | undefined;
const preselectedId = lastJudgeModelId ?? state.harness.getCurrentModelId() ?? undefined;And for line 111-113:
// Save judge preference for next time
const s = loadSettings();
+ if (!s.models) s.models = {};
(s.models as Record<string, unknown>).goalJudgeModel = model.id;
saveSettings(s);📝 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.
| const lastJudgeModelId = (settings.models as Record<string, unknown>).goalJudgeModel as string | undefined; | |
| const preselectedId = lastJudgeModelId ?? state.harness.getCurrentModelId() ?? undefined; | |
| const lastJudgeModelId = (settings.models as Record<string, unknown> | undefined)?.goalJudgeModel as string | undefined; | |
| const preselectedId = lastJudgeModelId ?? state.harness.getCurrentModelId() ?? undefined; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mastracode/src/tui/commands/goal.ts` around lines 97 - 98, Guard against
missing settings.models by using optional chaining and nullish coalescing when
reading and writing the judge model: change the access that computes
lastJudgeModelId (currently using (settings.models as Record<string,
unknown>).goalJudgeModel) to safely read settings.models?.goalJudgeModel as
string | undefined and compute preselectedId with the nullish fallback as now;
likewise update the save path that writes into settings.models to ensure
settings.models is initialized first (e.g., ensure settings.models =
settings.models ?? {} before assigning goalJudgeModel) so reads/writes of
goalJudgeModel cannot throw; adjust references in this file to use
lastJudgeModelId and preselectedId as before.
When sendMessage creates a new thread during goal flow, the thread_created event was loading from empty metadata and clearing the in-memory goal. Now if a goal is already in memory when a new thread is created, we save it to the new thread instead of loading empty metadata. Co-Authored-By: tyler <tylerdbarnes@gmail.com>
|
Review from MastraCode: Thanks for adding this — the overall shape looks good. Keeping the Ralph/goal loop in the TUI layer is a nice low-blast-radius approach, and I like that queued user input preempts automatic continuation. A few things I’d want addressed or at least confirmed before merge:
None of these block the direction of the feature for me, but I think at least the abort persistence issue and some targeted tests should be fixed before merging. |
…uracy, judge display, status wording - Add max cycles input popup (default 20, user editable) after model picker - Improve judge prompt to be better at detecting completion (lean toward 'done') - Show judge decision in chat with blue bordered 'Judge' component - Change status line from 'goal X/20' to 'X/20 attempts' (remaining/total) - Fix: persist goal pause to thread on user-initiated abort - Fix: only copy goal to new thread if it was just set (turnsUsed === 0) Co-Authored-By: tyler <tylerdbarnes@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
mastracode/src/tui/commands/goal.ts (1)
99-100:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winGuard
settings.modelsbefore reading or writinggoalJudgeModel.If
loadSettings()returns an object without amodelsbag, both accesses here throw and/goalfails before the picker opens or before the selection is saved.Suggested fix
const settings = loadSettings(); - const lastJudgeModelId = (settings.models as Record<string, unknown>).goalJudgeModel as string | undefined; + const lastJudgeModelId = (settings.models as Record<string, unknown> | undefined)?.goalJudgeModel as + | string + | undefined; const preselectedId = lastJudgeModelId ?? state.harness.getCurrentModelId() ?? undefined; ... // Save judge preference for next time const s = loadSettings(); + s.models ??= {}; (s.models as Record<string, unknown>).goalJudgeModel = model.id; saveSettings(s);Also applies to: 114-115
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mastracode/src/tui/commands/goal.ts` around lines 99 - 100, The code reads and writes settings.models.goalJudgeModel without guarding that settings.models exists; update the logic around loadSettings(), lastJudgeModelId, and any save path to check for settings.models (use optional chaining when reading: settings?.models?.goalJudgeModel or assign a typed empty object) and ensure you initialize settings.models = {} before writing to it so accessing or setting goalJudgeModel in the goal command (the variables settings, lastJudgeModelId and any save path near lines 114-115) cannot throw when models is undefined.mastracode/src/tui/handlers/agent-lifecycle.ts (1)
179-193:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRe-check queue and goal state after the async judge finishes.
This promise resolves later. If the user queues input or pauses/clears the goal while the judge call is running, the current code can still auto-send the continuation first, and
getGoal()!can be null by the time the result comes back.Suggested guard
state.goalManager .evaluateAfterTurn(state) .then(({ continuation, judgeResult }) => { + const goal = state.goalManager.getGoal(); + // Display the judge result in chat if available - if (judgeResult) { - const goal = state.goalManager.getGoal()!; + if (judgeResult && goal) { const judgeComponent = new JudgeDisplayComponent(judgeResult, goal.turnsUsed, goal.maxTurns); state.chatContainer.addChild(judgeComponent); state.ui.requestRender(); } if (continuation) { - const goal = state.goalManager.getGoal()!; + const freshGoal = state.goalManager.getGoal(); + if (!freshGoal || freshGoal.status !== 'active' || state.pendingQueuedActions.length > 0) { + ctx.updateStatusLine(); + return; + } - showInfo(state, `Continuing toward goal (attempt ${goal.turnsUsed}/${goal.maxTurns})...`); + showInfo(state, `Continuing toward goal (attempt ${freshGoal.turnsUsed}/${freshGoal.maxTurns})...`); ctx.fireMessage(continuation); } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mastracode/src/tui/handlers/agent-lifecycle.ts` around lines 179 - 193, The async evaluateAfterTurn handler must re-check that a goal still exists and that queued/paused state hasn't changed before using getGoal() or auto-sending a continuation: after state.goalManager.evaluateAfterTurn(...) resolves, guard every use of state.goalManager.getGoal() (used for JudgeDisplayComponent construction, showInfo, and before calling ctx.fireMessage) by verifying the goal is non-null (and optionally that its turnsUsed/maxTurns or an expected identifier matches the one captured before awaiting) and skip displaying the judge or firing the continuation if the goal was cleared/changed or user input was queued/paused; update the branches around JudgeDisplayComponent, state.chatContainer.addChild, state.ui.requestRender, showInfo, and ctx.fireMessage to perform these guards.mastracode/src/tui/goal-manager.ts (1)
266-269:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestate the goal here instead of replaying raw judge output.
This synthetic user turn is built from free-form
judgeReason, so the judge can steer the next turn and the original objective can drift once the initial/goalmessage falls out of context.Suggested fix
- private buildContinuationPrompt(judgeReason: string): string { + private buildContinuationPrompt(_judgeReason: string): string { const turn = this.goal!.turnsUsed; const max = this.goal!.maxTurns; - return `Continue working toward the goal. (Turn ${turn}/${max}: ${judgeReason})`; + const objective = this.goal!.objective; + return `Continue working toward this goal: "${objective}" (attempt ${turn}/${max}). Review your last response, identify the remaining work, and continue.`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@mastracode/src/tui/goal-manager.ts` around lines 266 - 269, The buildContinuationPrompt currently injects free-form judgeReason into the synthetic user turn, allowing the judge to steer objectives; change it to restate the original goal text from this.goal (e.g., use this.goal!.description or this.goal!.objective) and include judge feedback only as a short, quoted summary or label (e.g., "Judge feedback: <short summary>"). Update buildContinuationPrompt to produce a prompt like "Continue working toward the goal: <goal text> (Turn X/Y). Judge feedback: <brief summary>" so the original objective stays primary while still surfacing reviewer notes; use this.goal!.turnsUsed and this.goal!.maxTurns for the turn counter and sanitize/limit judgeReason when included.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@mastracode/src/tui/commands/goal.ts`:
- Around line 60-70: The resume command currently only checks if goal.status ===
'active' and proceeds for other states, which allows a 'done' goal to trigger
goalManager.resume(), saveToThread, and state.harness.sendMessage; update the
logic in the /goal resume handler to also block when goal.status === 'done' (or
the completed status your model uses) by returning early (showing an informative
ctx.showInfo) so that goalManager.resume(), goalManager.saveToThread(state), and
the subsequent state.harness.sendMessage call are not executed for completed
goals.
In `@mastracode/src/tui/components/judge-display.ts`:
- Around line 49-54: The padLine method currently returns the full text when
visibleLength >= width, causing long result.reason strings to overflow the UI;
update padLine to clamp/truncate ANSI-containing strings to the target width
instead of returning them unchanged: when visibleLength >= width, compute a
truncated visible portion of text that fits width (e.g., take the first width-1
visible characters and append an ellipsis '…'), but preserve original ANSI
escape sequences around the truncated content (you can continue to use
stripAnsi(text) for measuring and then rebuild the truncated string by walking
the original text and copying ANSI sequences plus visible characters until the
limit is reached), so padLine always returns a string whose visible length <=
width (and still works for texts with ANSI codes like result.reason).
In `@mastracode/src/tui/event-dispatch.ts`:
- Around line 155-160: The current check using currentGoal.turnsUsed === 0 is
unsafe; instead add an explicit one-shot bootstrap flag on the goal manager
(e.g., GoalManager.markBootstrapNextThread() / goalManager.bootstrapPending or
similar) that the /goal command sets when it creates a thread, then modify the
dispatch logic (replace the turnsUsed check in event-dispatch.ts where
state.goalManager.getGoal() and saveToThread(state) are called) to only
saveToThread when that bootstrap flag is set, and ensure the goal manager clears
the bootstrap flag immediately after successful saveToThread (and on failure) so
it won’t apply to subsequent threads.
In `@mastracode/src/tui/status-line.ts`:
- Around line 133-137: The status-line shows remaining attempts instead of
attempts used, causing it to read backwards; in the block that reads the goal
via state.goalManager.getGoal() and builds goalLabel, change the expression so
it displays turnsUsed/ maxTurns (i.e.
`${goalState.turnsUsed}/${goalState.maxTurns} attempts`) when goalState?.status
=== 'active' instead of `${goalState.maxTurns -
goalState.turnsUsed}/${goalState.maxTurns}` so it matches the rest of the UI
(refer to goalState, goalLabel, goalState.turnsUsed, and goalState.maxTurns).
---
Duplicate comments:
In `@mastracode/src/tui/commands/goal.ts`:
- Around line 99-100: The code reads and writes settings.models.goalJudgeModel
without guarding that settings.models exists; update the logic around
loadSettings(), lastJudgeModelId, and any save path to check for settings.models
(use optional chaining when reading: settings?.models?.goalJudgeModel or assign
a typed empty object) and ensure you initialize settings.models = {} before
writing to it so accessing or setting goalJudgeModel in the goal command (the
variables settings, lastJudgeModelId and any save path near lines 114-115)
cannot throw when models is undefined.
In `@mastracode/src/tui/goal-manager.ts`:
- Around line 266-269: The buildContinuationPrompt currently injects free-form
judgeReason into the synthetic user turn, allowing the judge to steer
objectives; change it to restate the original goal text from this.goal (e.g.,
use this.goal!.description or this.goal!.objective) and include judge feedback
only as a short, quoted summary or label (e.g., "Judge feedback: <short
summary>"). Update buildContinuationPrompt to produce a prompt like "Continue
working toward the goal: <goal text> (Turn X/Y). Judge feedback: <brief
summary>" so the original objective stays primary while still surfacing reviewer
notes; use this.goal!.turnsUsed and this.goal!.maxTurns for the turn counter and
sanitize/limit judgeReason when included.
In `@mastracode/src/tui/handlers/agent-lifecycle.ts`:
- Around line 179-193: The async evaluateAfterTurn handler must re-check that a
goal still exists and that queued/paused state hasn't changed before using
getGoal() or auto-sending a continuation: after
state.goalManager.evaluateAfterTurn(...) resolves, guard every use of
state.goalManager.getGoal() (used for JudgeDisplayComponent construction,
showInfo, and before calling ctx.fireMessage) by verifying the goal is non-null
(and optionally that its turnsUsed/maxTurns or an expected identifier matches
the one captured before awaiting) and skip displaying the judge or firing the
continuation if the goal was cleared/changed or user input was queued/paused;
update the branches around JudgeDisplayComponent, state.chatContainer.addChild,
state.ui.requestRender, showInfo, and ctx.fireMessage to perform these guards.
🪄 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: 334d2622-a53e-457b-b998-7d583d6639bf
📒 Files selected for processing (7)
mastracode/src/tui/commands/goal.tsmastracode/src/tui/components/goal-cycles-dialog.tsmastracode/src/tui/components/judge-display.tsmastracode/src/tui/event-dispatch.tsmastracode/src/tui/goal-manager.tsmastracode/src/tui/handlers/agent-lifecycle.tsmastracode/src/tui/status-line.ts
| private padLine(text: string, width: number): string { | ||
| const visibleLength = stripAnsi(text).length; | ||
| if (visibleLength >= width) { | ||
| return text; | ||
| } | ||
| return text + ' '.repeat(width - visibleLength); |
There was a problem hiding this comment.
Clamp or wrap long judge reasons before returning the row.
When visibleLength >= width, this returns the full string unchanged. A long result.reason will overflow the box and break the right border alignment.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@mastracode/src/tui/components/judge-display.ts` around lines 49 - 54, The
padLine method currently returns the full text when visibleLength >= width,
causing long result.reason strings to overflow the UI; update padLine to
clamp/truncate ANSI-containing strings to the target width instead of returning
them unchanged: when visibleLength >= width, compute a truncated visible portion
of text that fits width (e.g., take the first width-1 visible characters and
append an ellipsis '…'), but preserve original ANSI escape sequences around the
truncated content (you can continue to use stripAnsi(text) for measuring and
then rebuild the truncated string by walking the original text and copying ANSI
sequences plus visible characters until the limit is reached), so padLine always
returns a string whose visible length <= width (and still works for texts with
ANSI codes like result.reason).
|
Re: MastraCode review feedback — addressed in 659343f: 3. Abort pause persistence — Added 4. Thread creation goal copy — Now only saves the goal to a new thread if 1. Automated tests — Agreed this needs coverage. Will add targeted tests as a follow-up. 2. Full Test Suite — This failure is pre-existing and unrelated to mastracode changes (confirmed in prior CI runs on main). Lint, Build, E2E, Memory, and Combined Store checks all pass. |
…yword-match - Judge now uses step-by-step reasoning: what does the goal require? what was produced? are all requirements met? - Continuation message includes specific judge feedback so the agent knows what's still missing - Parser scans for decision keyword (handles models that add preamble) - Increased maxOutputTokens to 300 for proper reasoning Co-Authored-By: tyler <tylerdbarnes@gmail.com>
Add plan approval support for starting goals directly from submitted plans, plus goal-enabled slash commands and skills under /goal/<name>. Document goal usage, preserve multiline goal objectives, improve goal status/resume behavior, and add the pr-triage goal command for prioritized PR review. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
…and' into devin/1777655074-goal-slash-command # Conflicts: # mastracode/README.md # mastracode/src/agents/__tests__/workspace-skill-paths.test.ts # mastracode/src/tui/command-dispatch.ts # mastracode/src/tui/commands/goal.ts # mastracode/src/tui/commands/index.ts # mastracode/src/tui/components/goal-cycles-dialog.ts # mastracode/src/tui/components/help-overlay.ts # mastracode/src/tui/components/judge-display.ts # mastracode/src/tui/event-dispatch.ts # mastracode/src/tui/goal-manager.ts # mastracode/src/tui/handlers/agent-lifecycle.ts # mastracode/src/tui/setup.ts # mastracode/src/tui/state.ts # mastracode/src/tui/status-line.ts
Collapse the goal-related Mastra Code changesets into a single minor entry while keeping the Harness metadata change as a core patch. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Configure the goal judge agent with the same provider compatibility and stream error processors used by Mastra Code agent calls so judge evaluations behave consistently across providers. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Explain the new /goal command from the reader's perspective so release notes do not assume prior context about the feature. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
…oals (Ralph loop) (mastra-ai#16065) ## Description Implements the `/goal` slash command for mastracode: a persistent cross-turn goal loop where a separate judge model evaluates the assistant after each completed turn and either marks the goal done, asks the assistant to continue, or waits at an explicit user checkpoint. Goal state is persisted in thread metadata so it survives thread switches/restarts. The loop is designed to be preemptible: queued user messages, `/goal pause`, and `/goal clear` take priority over automatic continuations, and composer input is locked while the judge is evaluating to avoid race conditions. ## Features - `/goal <text>` — Set a standing goal and start the loop. - `/goal` or `/goal status` — Show current goal state, usage, and judge model. - `/goal pause` — Pause the continuation loop. - `/goal resume` — Resume a paused goal and send a continuation. - `/goal clear` — Drop the goal entirely. - `/judge` — Configure global goal judge defaults: judge model and max cycles. - Ctrl+C/Esc on empty input pauses an active goal. - Status line shows judge progress, e.g. `judge 3/50`. - Default max cycles is 50. - Judge decisions support `done`, `continue`, and `waiting`. - Judge failures fail closed by pausing the goal instead of looping blindly. - Goal judge threads are marked as forked subagent threads so they do not appear as normal resumable threads. ## Judge behavior The configured judge model evaluates the latest user/assistant context after each assistant turn: - `done` marks the goal complete. - `continue` injects a system-reminder continuation prompt so the assistant keeps working. - `waiting` keeps the goal active but does not auto-continue, for goals that explicitly require a human/user checkpoint. The judge can also answer `ask_user` tool prompts during goal mode so the main assistant can keep working autonomously unless the goal explicitly requires human verification. ## Safety and preemption - User follow-up messages and queued slash commands are drained before goal continuations. - Goal continuation re-checks the active goal id/status after async judge evaluation before firing. - GoalManager ignores stale judge results if the goal was paused, cleared, or replaced while judging. - Composer submissions and most slash commands are blocked while the judge is evaluating; `/goal pause`, `/goal clear`, and `/exit` remain available as escape hatches. - Starting `/goal` after `/new` creates and tags a fresh thread instead of reusing the previous current thread. ## Persistence and rendering - Goal state is saved to thread metadata. - Initial goal and terminal judge results are persisted as user-role system reminders so the assistant treats them as external steering, not its own prior output. - Goal/judge reminders render as blue system reminder boxes with judge metadata separated from model-visible text. ## Related Issue(s) Requested via Slack: https://kepler-bej6556.slack.com/archives/C0ACHFXNK7T/p1777654125743969 ## Type of Change - [ ] Bug fix (non-breaking change that fixes an issue) - [x] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [x] Test update ## Checklist - [ ] I have made corresponding changes to the documentation (if applicable) - [x] I have added tests that prove my fix is effective or that my feature works - [ ] I have addressed all Coderabbit comments on this PR Link to Devin session: https://app.devin.ai/sessions/cd5f8a2e7d0440e78ffc59d6993cfb61 Requested by: @TylerBarnes --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tyler <tylerdbarnes@gmail.com> Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Description
Implements the
/goalslash command for mastracode: a persistent cross-turn goal loop where a separate judge model evaluates the assistant after each completed turn and either marks the goal done, asks the assistant to continue, or waits at an explicit user checkpoint.Goal state is persisted in thread metadata so it survives thread switches/restarts. The loop is designed to be preemptible: queued user messages,
/goal pause, and/goal cleartake priority over automatic continuations, and composer input is locked while the judge is evaluating to avoid race conditions.Features
/goal <text>— Set a standing goal and start the loop./goalor/goal status— Show current goal state, usage, and judge model./goal pause— Pause the continuation loop./goal resume— Resume a paused goal and send a continuation./goal clear— Drop the goal entirely./judge— Configure global goal judge defaults: judge model and max cycles.judge 3/50.done,continue, andwaiting.Judge behavior
The configured judge model evaluates the latest user/assistant context after each assistant turn:
donemarks the goal complete.continueinjects a system-reminder continuation prompt so the assistant keeps working.waitingkeeps the goal active but does not auto-continue, for goals that explicitly require a human/user checkpoint.The judge can also answer
ask_usertool prompts during goal mode so the main assistant can keep working autonomously unless the goal explicitly requires human verification.Safety and preemption
/goal pause,/goal clear, and/exitremain available as escape hatches./goalafter/newcreates and tags a fresh thread instead of reusing the previous current thread.Persistence and rendering
Related Issue(s)
Requested via Slack: https://kepler-bej6556.slack.com/archives/C0ACHFXNK7T/p1777654125743969
Type of Change
Checklist
Link to Devin session: https://app.devin.ai/sessions/cd5f8a2e7d0440e78ffc59d6993cfb61
Requested by: @TylerBarnes