🤖 feat(goals): long-horizon auto-continuation with budgets#3235
🤖 feat(goals): long-horizon auto-continuation with budgets#3235
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
/coder-agents-review |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa89082972
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
First-pass review (Netero only). This is a mechanical scan; the full review panel has not yet reviewed this PR.
Netero raised 6 findings. 5 were dropped during verification: 3 cited an em-dash convention that does not exist in mux, and 2 cited a Storybook-only testing convention that mux does not follow (88 standalone .test.tsx files exist in src/browser). 1 P2 survives.
The surviving finding is a permanently empty UI section that ships to users with no path to displaying real data. The panel will review after this is addressed.
🤖 This review was automatically generated with Coder Agents.
|
@codex review P1 issues addressed in
Please take another look. |
|
/coder-agents-review |
There was a problem hiding this comment.
Round 2 is blocked. The open finding from round 1 has no author response and the code is unchanged.
DEREM-6 (P2, GoalTab.tsx:374-380): "Recent continuations" section is a permanent placeholder with no data source. No continuations prop, no schema field, no data pipeline. The author's e488c4ebd commit addressed the two Codex P1 findings but did not address or acknowledge DEREM-6.
Further review is blocked until the author responds to DEREM-6 (fix, acknowledge, or contest) or pushes a commit addressing it. The full review panel will run once this is resolved.
🤖 This review was automatically generated with Coder Agents.
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@coder-agents-review DEREM-6 addressed in the latest force-push: deleted the empty "Recent continuations" section from The full review panel can run now. |
|
/coder-agents-review |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a26bcdd0a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Two new P1s addressed in
Regression tests added for both. |
|
/coder-agents-review |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Second Netero pass (round 3). The full review panel has not yet reviewed this PR.
DEREM-6 (placeholder UI) verified fixed. 4 new findings: 1 P2, 3 P3. The P2 is a test reliability issue (raw setTimeout for coordination, violating mux AGENTS.md). The P3s are dead code, missing test coverage, and a silently divergent helper. The panel will review after the P2 is addressed.
🤖 This review was automatically generated with Coder Agents.
|
@codex review Two more rounds addressed in |
|
/coder-agents-review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 66c58bfbd2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed non-user abort mutation ordering: WorkspaceService no longer drains queued goal mutations in its stream-abort listener; AgentSession now drains them after abort accounting, preserving attribution for the in-flight stream while still applying non-user abort mutations at the turn boundary. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 68fb66ce67
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed stream-end drain ordering: WorkspaceService no longer drains queued goal mutations from its global stream-end listener; AgentSession remains responsible for draining after stream accounting and before continuation checks. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fdc36f2414
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed budget wrap-up reservation after the wrap stream overwrites the last-stream stamp: the reservation now validates the same goal and non-user origin without requiring the old sequence number, so a successful wrap stream can still mark |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f7aa22185
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed limit-specific wrap-up text: the budget-limit synthetic prompt now distinguishes budget exhaustion from turn-cap exhaustion (and both), omits misleading budget-limit text when no dollar budget exists, and keeps accounting lines accurate. Added prompt regression coverage for turn-cap-only goals. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e580389ad3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed under-budget child completion re-arming: after child attribution, active parent goals now re-request/arm their kickoff continuation, while budget/turn-limit transitions still arm the budget wrap-up. Added WorkspaceGoalService regression coverage for a deferred parent continuation resuming after the child completes under budget. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93936575d5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed palette blank budget semantics: the Goal palette now treats an intentionally blank budget field as explicit no-budget ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8d07733b03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed mapped-model pricing: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8859916582
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed the pricing guard issues:
Validated budgetPricing, ChatInput-adjacent command/model guard coverage, GoalTab budget editing, and |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de16456a7c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed slash goal pricing guard config: current-model pricing checks now load provider config via the API and pass it to |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9dd8b8e513
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed palette provider-config pricing: command palette goal creation now loads provider config and passes it into |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e1e2e9f702
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed palette selected-model validation: command palette goal budget checks now prefer the selected send-options model from storage, falling back to transcript currentModel only when no selected model is available. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7da964c2b4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed palette model fallback: palette budget validation now checks whether a workspace model is actually persisted; it only uses stored send options when a model preference exists, otherwise it falls back to the transcript current model. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a7ca91e7a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed GoalTab model fallback: budget edits now check for an actually persisted workspace model preference; when absent, they validate against the transcript current model before falling back to send defaults. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5faa1d130
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (result.success && result.data.status === "active") { | ||
| this.armKickoffContinuationIfIdle(input.workspaceId, result.data); | ||
| } |
There was a problem hiding this comment.
Arm wrap-up when budget edits exhaust the goal
When a user edits the budget/turn cap to a value that is already exhausted (for example lowering the budget below costCents, or creating a goal with $0), applyBudgetDrivenStatus persists the goal as budget_limited, but this post-write hook only arms kickoff continuations for active goals. Since no pending candidate or stream stamp is created on this mutation path, checkGoalContinuationEligibility never runs the one-shot budget wrap-up and the goal stays stuck in budget_limited until manual intervention or restart recovery.
Useful? React with 👍 / 👎.
Adds a workspace-scoped, persistent **Goal** primitive that the agent
auto-continues against an explicit dollar budget after each turn ends.
Inspired by OpenAI Codex's `/goal` but adapted to Mux's primitives.
- `/goal "<objective>" [--budget $X[.YY]] [--turn-cap N]` slash command
plus parity in the command palette (set / pause / resume / complete /
clear / edit budget). Workspaces hold at most one active goal.
- New `IdleDispatcher` that serializes per-workspace dispatches and is
shared with `HeartbeatService`; goals get priority 100 (suppresses
background heartbeats while a goal is active).
- Cost / budget / turn / elapsed accounting persisted to
`~/.mux/sessions/<workspaceId>/goal.json` with atomic writes and an
optimistic-concurrency `expectedGoalId` on `setGoal`.
- Auto-continuation: at every stream-end, if the goal is `Active` and
the workspace is idle, a synthetic `<untrusted_objective>`-wrapped
user message is injected to drive the next turn (60s cooldown,
serialized globally).
- BudgetLimited wrap-up: once `costCents >= budgetCents`, exactly one
graceful "stop and summarize" continuation fires before further
dispatches are suppressed.
- Auto-pause hooks: any non-synthetic user message pauses an active
goal before streaming; partial-message recovery on startup arms a
user-acknowledgment gate that blocks continuations until the user
posts again.
- Sub-agent task costs are attributed up to the parent's active goal
on task completion (children do not inherit the goal).
- Forked workspaces inherit a snapshot of the parent goal but reset
accounting and default to `Paused` to avoid runaway loops.
- Two model-facing tools: `get_goal` (read-only) and
`complete_goal(summary)` — conditionally injected when the GOALS
experiment is on, the workspace has an active/budget-limited goal,
and the agent is exec-like with editing capabilities.
- UI: left-sidebar `Target` pill on every workspace with a goal,
conditional right-sidebar Goal tab (status + accounting +
Pause/Resume/Mark complete/Clear), a settings section under
Experiments, and custom transcript cards for `get_goal` /
`complete_goal` that mirror the GoalTab styling.
- Live snapshot fan-out via `WorkspaceService.emit("activity")` so
pill + tab refresh without a reload.
- Telemetry: lifecycle events (`goal_created/replaced/cleared/paused/
resumed/completed/budget_limited/budget_changed/continuation_fired/
wrapup_fired/crash_gate_set`) emitted through `AnalyticsService`.
Gated by `EXPERIMENT_IDS.GOALS` (off by default, user-overridable).
- Dollars (cents internally), not tokens, because Mux supports many
models with different token economics. `turnCap` is a secondary
guardrail. Wall-clock time is display-only.
- Compaction streams are excluded from goal accounting; failed /
aborted streams are counted (provider cost is still paid).
- Synthetic continuation prompts XML-escape the objective inside an
`<untrusted_objective>` block and explicitly tell the model to
treat the objective as data, not instructions.
---
_Generated with `mux` • Model: `anthropic:claude-opus-4-7` • Thinking: `max` • Cost: `$768.23`_
<!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=max costs=768.23 -->
|
@codex review Addressed slash goal transcript-model fallback: ChatInput now passes the workspace transcript currentModel into slash command context, and goal budget pricing checks use persisted selected model when present, otherwise the transcript model before falling back to send defaults. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd6f0c8244
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "goal-show", | ||
| "goal-set", | ||
| "goal-budget", | ||
| "goal-clear", |
There was a problem hiding this comment.
Dispatch the new goal lifecycle commands
The dispatcher only enters the workspace-command switch when WORKSPACE_ONLY_COMMAND_TYPES.has(parsed.type) is true, but the new parsed types goal-pause, goal-resume, and goal-complete are missing from this set. As a result, /goal pause, /goal resume, and /goal complete parse successfully but fall through to the default path with no toast or action instead of reaching the cases in processSlashCommand.
Useful? React with 👍 / 👎.
| if (parsed.type === "goal-resume") { | ||
| const result = await setGoalWithSingleConflictRetry(context, { status: "active" }); | ||
| if (!result.success) { |
There was a problem hiding this comment.
Gate goal resume on priced models
When a paused goal still has a budget and the selected/persisted model has no pricing data (for example from settings saved before this gate existed or after provider pricing changes), this resumes the goal and shows success without the pricing check used by goal creation/budget updates. setGoalImmediately then arms kickoff continuation for active goals, but the synthetic send is rejected by the backend pricing gate, leaving an active goal that does not continue. Resume should reject with the same unpriced-model feedback before setting status: "active" in that scenario.
Useful? React with 👍 / 👎.
Summary
Adds a workspace-scoped, persistent Goal primitive that the agent auto-continues against an explicit dollar budget after each turn ends. Inspired by OpenAI Codex's
/goalbut adapted to Mux's primitives: synthetic visible user messages instead of adeveloperrole, dollar budgets instead of tokens (we already track normalized cost viasessionUsageService), a sharedIdleDispatcherconsolidating heartbeats and goal continuations, and conditionally-injected model tools.Gated end-to-end by
EXPERIMENT_IDS.GOALS(off by default, user-overridable). Off the experiment, the only externally observable change is the new ESM module graph; runtime behavior is identical tomain.Background
Long-horizon agent runs on Mux today require the user to nudge the agent ("continue", "what next") at every stream-end. Codex solved this with
/goal, but its model is token-budget + developer-role. Mux supports many providers with wildly different token economics, has no developer role, and already has an idle scheduler shared by heartbeats — so the design adapts the spec rather than copying it. The accompanying RFC (separategoals-planningbranch — not part of this PR to keep history clean) covers the full architecture, state diagram, accounting boundaries, and edge cases.Implementation
Backend
WorkspaceGoalService(new) owns goal lifecycle. Persists each workspace's goal at~/.mux/sessions/<id>/goal.jsonviawriteFileAtomic+ per-workspace file locks. Optimistic-concurrency onsetGoalvia an optionalexpectedGoalIdreturns a typedResult<GoalRecordV1, GoalSetError>so the frontend can retry without throwing.IdleDispatcher(new, also extracted fromHeartbeatService) serializes per-workspace dispatches with priority-ordered consumers — goals = 100, heartbeats = 50 — so an active goal naturally suppresses heartbeats. Global concurrency cap of 1.Active→Paused(manual user message or/goal pause),Active→BudgetLimited(cost ≥ budget),BudgetLimited→ exactly one wrap-up continuation → done,*→Complete(modelcomplete_goalor/goal complete). Setting an identical objective is idempotent (preservesgoalId+ accounting).agentSessionstream-end, if the goal isActiveand the workspace is idle, anIdleDispatchercandidate fires a synthetic user message wrapping the objective in<untrusted_objective>. 60s cooldown, gated byrequireUserAcknowledgmentSinceMsif a partial assistant message was recovered on startup.taskcompletion. Children do not inherit goals (avoids recursive budget exhaustion).Paused.get_goal()andcomplete_goal(summary)are conditionally registered when GOALS is on, the workspace has an active/budget-limited goal, and the agent has editing capabilities.create_goalwas deliberately omitted — goals must be user-initiated.Frontend
Target-icon pill on workspaces with a goal (live status, e.g.Target $0.04 / $1.00).get_goalandcomplete_goal(replacing the generic▶ tool_namerow), styled to match the GoalTab./goal "<obj>",/goal pause,/goal resume,/goal complete,/goal clear,/goal budget. All have command-palette equivalents.WorkspaceGoalService.pushSnapshotinvokes a callback wired incoreServicestoworkspaceService.emit("activity"), so the pill + tab refresh without a reload.Telemetry
AnalyticsService.recordGoalLifecycleEventemits a closed set of events:goal_created/replaced/cleared/paused/resumed/completed/budget_limited/budget_changed/continuation_fired/wrapup_fired/crash_gate_set. All carry buckets, never raw text.Validation
make static-checkgreen (typecheck + ESLint + Prettier + docs link checking)WorkspaceGoalService,IdleDispatcher,agentSession.goalAutoPause,goal.test(tools),GoalTab,goalToolUtils, slash-command parser, command palette, and the dispatcher registrydev-server-sandbox: kicked off two goals (one auto-completing, one budget-limited), confirmed the synthetic continuation fires within ~180ms ofsetGoal, the cost accumulator + budget gating advance correctly, and Pause/Resume/Complete/Clear all round-trip through the UIRisks
Medium. The dispatcher serialization, accounting accumulator, and stream-end hook all touch hot paths shared with heartbeats and sub-agents. Mitigations:
EXPERIMENT_IDS.GOALSgates every runtime branch — off by defaultIdleDispatcherwas extracted as a pure refactor in the first wave (no behavior change toHeartbeatService) and covered by 28 dedicated tests before goal logic was layered onworkspaceFileLocks+writeFileAtomic; corruptedgoal.jsonis treated as no goal rather than crashing the workspacegetGoalContinuationRuntimeStatereportingisInitializingindefinitely)Pains
The biggest sink was discovering, mid-dogfood, that the auto-continuation loop never fired because
WorkspaceService.getGoalContinuationRuntimeStatereturnedisInitializing: initState != nullinstead ofinitState?.status === "running".InitStateManagerkeeps the persisted status forever after init succeeds, so the eligibility check kept deferring withreason: "initializing". Existing tests stubbed the bridge, so the bug went undetected. Now covered by an integration test wiring the realWorkspaceServicethrough the real dispatcher.A separate snag:
coreServices.ts(used bymux server/ dev-server-sandbox) was constructing the goal service but never callingregisterGoalContinuationConsumer. Now wired in bothcoreServicesandserviceContainer.