Goals
Added in: @mastra/core@1.42.0
A goal is a durable, thread-scoped objective: a standing instruction the agent keeps working toward across loop iterations until a judge model decides it is satisfied or a run budget is exhausted. The objective is persisted in thread state, so it survives reloads and is evaluated in-loop — even when a new message arrives in the middle of an already-running turn.
Goals build on the same machinery as isTaskComplete: an LLM-as-judge scores the agent's output each iteration and gates the loop. The difference is that a goal is durable (stored in thread state, not passed per call) and is set and updated through Agent methods rather than per-stream() options.
Goals are experimental and may change in a future release.
When to use goalsDirect link to When to use goals
Use a goal when you want an agent to keep working toward a single objective across many iterations and messages, without re-supplying the success criteria on every call:
- A standing objective the agent should pursue until a judge says it's done.
- Work that should continue across mid-run messages (a message delivered into a live run is still judged against the goal).
- An objective that must persist across thread reloads or process restarts.
For a one-off completion check within a single stream() call, use isTaskComplete instead.
QuickstartDirect link to Quickstart
Goals require a configured storage backend and a memory-backed thread. Add a goal config to the agent — a judge model is required for the goal to do anything — then set an objective for a thread:
import { Agent } from '@mastra/core/agent'
const worker = new Agent({
name: 'worker',
instructions: 'You complete software tasks end to end.',
model: 'openai/gpt-5.5',
memory,
goal: {
judge: 'openai/gpt-5-mini',
maxRuns: 50,
},
})
// Set the durable objective for a thread.
await worker.setObjective('Add and test a /health endpoint', {
threadId,
resourceId,
})
// The objective is judged each iteration until it's complete or maxRuns is hit.
const stream = await worker.stream('Start working on the goal', {
memory: { thread: threadId, resource: resourceId },
})
The goal config auto-registers the state-signal projection, so the model always sees the current objective as <current-objective> in its context — no extra setup needed.
How the goal step worksDirect link to How the goal step works
A goal step runs inside the agentic execution loop, right after isTaskComplete. On a real candidate answer it scores the conversation against the objective and gates the loop:
- Not satisfied, budget remaining → the loop continues; per-evaluation feedback is injected so the agent iterates.
- Satisfied → the loop stops and the objective is marked
done. - Budget exhausted (
runsUsed >= maxRuns) → the loop stops but the objective staysactive, so raisingmaxRunslater can resume it.
The step is a no-op for background-task, mid-tool-loop, and working-memory-only iterations — the same gating as isTaskComplete.
The judge model is the activation switch. If no judge resolves (neither the per-objective override nor the agent's goal.judge), the goal step does nothing: no scoring, no budget consumed, no goal chunk. Effective settings resolve as per-objective record value → agent goal config → built-in default (maxRuns 50, a default judge prompt).
By default the step uses a built-in LLM-as-judge scorer that returns 1 when the objective is achieved and 0 otherwise. Supply your own scorer with goal.scorer to customize judging.
const worker = new Agent({
name: 'worker',
instructions: 'You complete software tasks end to end.',
model: 'openai/gpt-5.5',
memory,
goal: {
// A resolver function lets you inject provider credentials and read the
// current judge selection at runtime; returning `undefined` keeps the
// goal step a no-op.
judge: ({ requestContext }) => resolveJudgeModel(requestContext),
maxRuns: 30,
prompt: 'Only mark the goal complete when tests pass.',
},
})
Each evaluation emits a typed goal stream chunk (GoalEvaluationPayload: objective, iteration, maxRuns, passed, status, results, reason, duration, timedOut, maxRunsReached, suppressFeedback) so a UI can show goal progress mid-run.
Managing the objectiveDirect link to Managing the objective
Control the objective for a thread with Agent methods. All of them no-op when the run is not memory-backed (they require storage and a threadId):
// Read the current objective record.
const record = await worker.getObjective({ threadId })
// Update options on the active objective (only provided fields are written;
// unset fields fall back to the agent's `goal` config).
await worker.updateObjectiveOptions({ threadId, maxRuns: 100 })
// Drop the objective.
await worker.clearObjective({ threadId })
Per-objective values written by setObjective / updateObjectiveOptions take precedence over the agent's goal config, and that precedence is remembered in thread state.
See the GoalEvaluationPayload in the ChunkType reference for the full goal chunk shape.
RelatedDirect link to Related
- Supervisor agents —
isTaskCompleteand the rubric scorer - Signal providers — how the objective is projected into context
- Memory storage — the storage backend goals require