Durable objectives with checkable success criteria — persisted locally, injected into every turn, and scored by a background judge that fails closed.
A goal is a long-lived objective that spans many tasks and sessions — "finish the product launch", "keep the docs accurate" — not a one-off request. Every goal carries two required parts: an objective stated concretely, and success criteria that describe how Mori will know it is done. A goal without checkable criteria cannot be created; create_goal rejects the call if either field is missing.
Goals are plain records in a local JSON file under the app's user-data directory (per profile, capped at the most recent 200). Nothing about a goal leaves your machine except the model calls you already make with your own API keys. Restarting the app changes nothing: goals load from disk on every turn.
Each record tracks status (active, paused, complete, blocked, failed), progress (0–100), an iteration counter against a hard cap, the current blocker, the single next action, the latest judge verdict, and completion evidence.
Mori manages goals through three tools, and you can edit the same store from the Goals UI — both paths write through the same upsert, so neither clobbers the other.
create_goal(objective, success_criteria, max_iterations?) — creates an active goal at 0% with a default iteration cap of 25. Returns the new id (g_ prefix).update_goal(id, ...) — records progress after meaningful work: progress % (clamped 0–100), status, blocker, next action, or completion evidence. The id argument also accepts a unique fragment of the objective text.list_goals() — one line per goal: id, status, progress, blocker, next action. Read-only, always permitted in agent mode, and safe to run in parallel with other read tools.Two behaviors worth knowing: setting evidence when progress is already 100 auto-flips the status to complete, and every update_goal call increments the iteration counter — the same counter the judge uses.
A typical lifecycle, as the agent runs it. Evidence is expected at completion — file paths, URLs, verified results — and the tool descriptions instruct the model to never mark complete without it.
create_goal({
objective: "Migrate the docs site to the new domain",
success_criteria: "All pages load at docs.example.com with zero 404s",
max_iterations: "12"
})
→ Created goal g_lxo3f2: "Migrate the docs site...". Track it with update_goal.
update_goal({ id: "g_lxo3f2", progress: "60", next_action: "Update DNS CNAME" })
→ Updated goal g_lxo3f2 — 60% · active · next: Update DNS CNAME
update_goal({ id: "g_lxo3f2", progress: "100",
evidence: "Crawled 214 pages at docs.example.com, 0 errors (crawl.log)" })
→ Updated goal g_lxo3f2 — 100% · completeGoals are not write-only bookkeeping. At the start of every turn Mori reloads the store, then injects your active and blocked goals into the turn context that rides along with your newest message — up to six, each with its id, objective, progress, current blocker, and next action.
The injected block carries a standing instruction: make progress on the relevant goal, call update_goal when you do, and mark complete only with evidence. The context is attached to a copy of the message, never to stored history, so your conversation log stays clean.
After each real turn, a background evaluator scores your active goals against what just happened. It is one cheap call on the fast variant of your configured provider — your own key, small token budget — covering up to three active goals at a time. For each goal it returns a progress score, a verdict (continue, complete, or blocked), a one-line next action, and a one-sentence reason, which lands on the goal as latestEval.
The judge is deliberately conservative and safe to run unattended:
complete only when the success criteria are demonstrably met by evidence in the turn, and completion stamps an evidence note on the goal.The judge is a scorer, not an actor. It updates goal state; it never triggers tools, and any actual work toward a goal still happens inside normal turns under the normal approval gates.
Every goal has a hard iteration cap (default 25, settable at creation, minimum 1). Both write paths count against it: each update_goal call and each judged turn increments the counter.
When an active goal reaches its cap, the system fails closed rather than looping forever. The judge flips the goal to blocked and appends an explicit blocker — iteration cap (N) reached — and the update_goal result warns that the goal needs your review before continuing. A capped goal is never silently continued; blocked goals stay visible in the turn context with their blocker, so Mori will surface the situation instead of grinding on it. You unblock by reviewing the goal and setting it back to active.
Goals and the task board solve different problems. Use a goal for an objective that outlives any single session and needs a definition of done. Use a board task for a discrete unit of work you want tracked or queued on the Kanban — tasks have lanes (inbox through archived), priorities, and results, and can link back to a goal via goal_id, so the natural pattern is one goal broken into several linked cards.
The board carries a safety invariant that goals do not need: the agent can only file cards into inbox, planned, or waiting_user. Only you can stage a card into ready — the lane the dispatcher auto-runs — by dragging it on the Board. If the model asks for ready, the card is re-filed as planned instead. Goal bookkeeping itself is metadata-only and low-risk, but it still runs inside the permission system: the goal tools sit in the file-writing category, are disabled entirely at the safe permission level, and any risky action taken in service of a goal goes through the same per-action approval gates as everything else.