Skip to main content

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.

note

Goals are experimental and may change in a future release.

When to use goals
Direct 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.

Quickstart
Direct 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:

src/mastra/agents/worker.ts
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 works
Direct 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 stays active, so raising maxRuns later 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.

src/mastra/agents/worker.ts
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 objective
Direct 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):

src/mastra/objective.ts
// 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.

note

See the GoalEvaluationPayload in the ChunkType reference for the full goal chunk shape.