Goals
personaforge/goals adds durable, thread-scoped objectives to an agent. A goal is a standing instruction the agent keeps working toward across loop iterations until a judge model decides it's satisfied, a run budget is exhausted, or the loop hits a step cap. Objectives persist in thread state, so they survive reloads and are still judged when a new message arrives mid-run.
import { InMemoryGoalStore, createSqliteGoalStore } from 'personaforge/goals';Quick start
Goals are configured on the agent and driven per-thread:
import { agent } from 'personaforge';
const worker = agent({
instructions: 'You complete software tasks end to end.',
model: 'openai/gpt-5', // main agent
goal: {
judge: 'openai/gpt-5-mini', // judge model
maxRuns: 50, // per-objective budget
},
});
// Set a durable objective scoped to a thread:
await worker.setObjective('Add and test a /health endpoint', {
threadId: 'thread-42',
resourceId: 'user-7',
});
// Each stream()/run() now works toward the objective in-loop:
const stream = await worker.stream('Start working on the goal', {
memory: { thread: 'thread-42', resource: 'user-7' },
});
// The loop emits `goal` events as the judge evaluates:
for await (const chunk of stream) {
if (chunk.type === 'goal') {
console.log('iteration', chunk.goal.iteration,
'passed?', chunk.goal.passed,
'reason:', chunk.goal.reason);
}
}How the loop uses the goal
Each model iteration:
- The judge (
GoalRunConfig.judge) receives the agent's current output. - If
passed→ the loop stops withfinishReason: 'stop'. - If not passed → the judge's
reasonis fed back as revision feedback and the loop continues. - The loop stops with
finishReason: 'max_runs'whenmaxRunsis exhausted,maxStepsforces a stop, or the judge produced no actionable feedback.
A StreamChunk with type: 'goal' carries the GoalEvaluation (see below) so you can observe progress in real time.
Objective records
The in-loop judge reads the thread's ObjectiveRecord:
interface ObjectiveRecord {
objective: string;
threadId?: string;
resourceId?: string;
maxRuns?: number; // per-objective budget override
runsUsed: number;
status: 'active' | 'done' | 'paused';
activeDurationMs?: number;
updatedAt: string;
prompt?: string; // per-objective judge prompt override
}Managing objectives at runtime
The agent surface exposes runtime goal management:
// Set / get / update / clear per-thread objectives:
await worker.setObjective('Fix the flaky test', { threadId: 't9', maxRuns: 20 });
const rec = await worker.getObjective({ threadId: 't9' });
await worker.updateObjectiveOptions({ threadId: 't9', maxRuns: 100, prompt: 'Be lenient about style.' });
await worker.clearObjective({ threadId: 't9' });Goal stores
In-memory (development / tests)
import { InMemoryGoalStore } from 'personaforge/goals';SQLite (production) — survives restarts
import { createSqliteGoalStore } from 'personaforge/goals';
const store = createSqliteGoalStore('./agent.db'); // requires better-sqlite3When using createAgent/agent, the factory auto-creates a SQLite goal store when AGENT_DB_PATH is set (falling back to in-memory if better-sqlite3 isn't installed). Pass goalStore explicitly to override:
const worker = agent({
instructions: '...',
goal: { judge: 'openai/gpt-5-mini' },
goalStore: createSqliteGoalStore('./prod.db'),
});LLM judges
The goal feature is built on personaforge/goals judges — LLM-as-judge scoring for the agentic loop:
import { createLlmJudge, createStaticJudge, createRubricScorer, createSchemaScorer } from 'personaforge/goals';
// LLM judge with a custom prompt:
const judge = createLlmJudge({
llm: myLlmProvider,
prompt: 'You are a strict completeness judge. Respond with JSON.',
});
// Deterministic predicate judge:
const staticJudge = createStaticJudge((text) => text.includes('DONE'));
// Rubric (checklist) scorer with a backing LLM judge:
const rubric = createRubricScorer({
judge,
criteria: [
{ description: 'lists acceptance criteria', required: true },
{ description: 'explains test strategy' },
],
requireAll: true,
});
// Schema-validated scorer:
const schemaScorer = createSchemaScorer(myOutputSchema);Judges return a JudgeVerdict: { passed, reason?, score? }.
Related pages
- Durable Agents — resumable runs that pair with goals.
- Learning — continuous improvement / feedback.
- Agents —
agent()options reference.