Skip to main content
Goals are defined on the Purpose page (Success Criteria and per-stage criteria). This page covers how goal completion is verified: the per-turn Auto-check evaluator and per-goal Goal Checks (owner code).

Why verification exists

Without verification, goal completion is self-reported: the agent decides it is done and calls the mark_goal_complete tool with a value for every goal. The platform validates that required values are present — but not that they are true. An agent could claim appointment_booked: true without any appointment existing. Two optional layers close that gap: The two compose: with Auto-check on, conversational goals are assessed by a small LLM pass while code-backed goals are verified by your Goal Check — and the code’s verdict always wins.

Where to edit Goal Checks

The same code is editable in two places (it is stored once, on the goal itself in the purpose section):
  1. Purpose page — each goal card has an Add Code Check button that opens an inline editor with a Test button.
  2. Events page — under the Goals & Handoff channel, each goal with code appears as a Goal Check row; create new ones by picking Channel → Goal → Event. Unlike ordinary event actions, a Goal Check is not fire-and-forget: its return value is the verdict.

The handler contract

Write the body of handle(goal, ctx) (JavaScript or Python — a pasted full handle definition also works) and return a verdict:
  • status — the verdict. partial means real progress that isn’t complete yet.
  • value — the confirmed value (persisted, shown to the agent, fed back as goal.candidate_value next time).
  • note — one short line of evidence, or what is still missing. Shown to the agent and in rejection messages, so make it actionable.
Returning a bare true/false or a bare status string also works. Anything unrecognizable fails closed to unmet.

When your code runs

Goals already verified met by code are skipped on subsequent end-passes (the gate always re-checks live). Sandbox time is billed per second; the Auto-check LLM pass is billed to the owner like other auxiliary calls.

The goal object

Your handler’s first argument:

The goal status map (goal.goals)

Each subscriber’s verified progress is persisted per goal (the same state that renders the [x] / [~] / [ ] checkboxes in the agent’s context). Your code receives all of it, keyed by goal key:
  • status is met, partial, unmet, or unchecked (never assessed yet — also the case for anonymous visitors, whose state has nowhere to persist).
  • tag is the goal’s positional citation tag — the same GOAL-1, GOAL-2, … labels shown on the Purpose page and in the agent’s own context. Tags are numbered per active checklist (top-level goals, or the active stage’s criteria).
Select a goal by name or by tag:
Tags are positional: inserting or reordering goals renumbers them. Prefer selecting by key in code you intend to keep; use tags for quick cross-references that mirror the on-screen labels.

The ctx object

The second argument is the same tool bridge custom code tools get — see the full reference at Custom tool ctx. In short:
  • await ctx.tools.<name>(input) — call any enabled built-in tool. Goal Checks get a curated, read-oriented bundle: subscriber memory + structured memory, knowledge-graph query, web search/fetch, calculate.
  • await ctx.audit(event, data) — record a structured audit note.
  • ctx identity fields (agentId, agentUserId, …) and plain fetch from inside the sandbox are also available.
The sandbox is fully isolated — no environment variables, no filesystem, 30-second timeout, and a cross-tool call cap.

Example 1 — simple: validate the claimed value

No I/O at all: the goal is met only when the claimed contact detail actually looks like an email address or phone number. Pure validation of goal.candidate_value. Goal definition (Purpose → Success Criteria):
Goal Check (body of handle(goal, ctx)):
The agent can no longer satisfy this goal with "will provide later" — the gate rejects the call and relays your note, so the agent knows exactly what to fix.

Example 2 — medium: verify against stored data

The goal claims a booking was recorded. The check doesn’t trust the claim — it reads the subscriber’s structured memory through ctx.tools and looks for the actual record, returning partial when a booking exists but is missing its confirmation. Goal definition:
Goal Check:
With Auto-check on, the agent’s context shows [~] appointment_booked (partially met; booking recorded but not yet confirmed…) until the confirmation lands — the agent knows to close the loop instead of declaring victory.

Example 3 — high complexity: external system + cross-goal ordering

A payment-collection goal that (a) refuses to pass until the other goals are verified, using the goal status map, (b) confirms the payment against an external API with plain fetch, and (c) uses stage_context from an earlier stage. This is the kind of check that makes a multi-stage funnel trustworthy end-to-end. Goal definition (on a checkout stage):
Goal Check:
Because the check gates mark_goal_complete, the stage cannot auto-advance past checkout until the payment provider itself confirms settlement — regardless of what the model believes.
Python works everywhere JS does — write the body of async def handle(goal, ctx) with the same return shape: return { "status": "met", "value": ... }.

Testing a check

  • Purpose page — the Test button under the editor runs your (unsaved) code in the real sandbox against the goal’s metadata and shows the verdict, note, runtime and provider. No subscriber state is touched; every entry in goal.goals reads unchecked.
  • APIPOST /api/test-goal-evaluator with { agent_name, goal_key, code?, sample_value? } (owner session required).

Semantics reference

  • One check per goal. Empty code = no check; the goal stays LLM-assessed (or presence-only when Auto-check is off).
  • Hard gate: a required code-backed goal must verify met for mark_goal_complete to succeed; a failing optional goal is subtracted from the optional-satisfied count. Verdicts are persisted even when the call is rejected.
  • Stage scoping: state is kept per stage (and _flat for stage-less agents), so reused keys across stages never collide. Tags number within the active checklist.
  • Anonymous visitors: no per-goal state is persisted (nothing to key it on); the gate still runs your code live at completion time.
  • Fail-open vs fail-closed: see the table under When your code runs.

Ask the AI Designer

The AI Designer can author all of this for you — goal definitions, evaluator_code Goal Checks, the auto_eval toggle, and fire-and-forget event actions. Try: “Add a goal ‘deposit paid’ with a code check that verifies the deposit against our structured memory record, and turn on Auto-check.”

Manage via the Management MCP

Goal Checks live on the purpose section — each criterion’s evaluator_code field (see the purpose field reference). Event actions live in the events section. Both are readable and writable through the Management MCP.