Files
stack/extensions/goal

goal — pi extension

Operator-set goal with a forced check<->proceed loop. /goal <text> activates a goal; the extension keeps the agent working and reporting until it declares the goal satisfied via the goal_report tool, then retains a recallable completed outcome and returns the session to normal operation.

Requirements record: docs/PRD.md in mosaic-brain (issue #52). Design locked with Jason 2026-08-28 (Q1-Q8).

Usage

/goal                  full current or completed goal, state, and counters
/goal <text>           set (or replace, with a notify) the active goal
/goal --max N <text>   set with a non-default run limit (default 25)
/goal stop             pause the loop, goal retained
/goal resume           continue a paused goal (kicks a check turn)
/goal clear            remove the goal entirely

GOAL_MAX_CHECKS env overrides the default cap for new goals (invalid values fall back to 25).

Loop mechanics

  1. before_agent_start appends the active goal and reporting instructions to the system prompt every turn, so the goal survives context growth and compaction.
  2. When the agent settles without the goal being satisfied, the extension injects a check prompt (sendUserMessage), forcing the next turn: the check<->proceed loop. Before reporting, follow the continuation loop in skills-local/ms-proactive-agent/SKILL.md.
  3. The agent reports via goal_report, the loop's only exit:
    • satisfied + evidence: completed outcome retained for recall, normal operation (evidence, reason, and in_progress structured progress use the ms-executive-update format — skills-local/ms-executive-update/SKILL.md — so the operator reads one shape everywhere)
    • blocked + reason: loop paused, reason surfaced to the operator
    • in_progress: evidence plus a structured progress record naming the nearest gate, owner, last live measurement, and next action
  4. Successful non-goal_report tool results create one observable work event. Measurement, action, and delegation reports require that event. Non-tool work requires a concrete artifact. Waiting requires an approved watch id or concrete next-check condition.
  5. Empty, malformed, unsupported, or duplicate in_progress reports do not reset the check counter. They increment a persisted no-progress counter and pause after three reports by default (GOAL_MAX_NO_PROGRESS_REPORTS). Repeated valid wait reports remain active without claiming new progress or triggering a false-positive pause.
  6. Esc (run abort) and run errors pause the loop instead of re-injecting; /goal resume continues. Operator-typed messages always flow through unchanged.
  7. No-report cap: N consecutive check turns without an accepted report auto-pause the loop and notify. Default 25 (GOAL_MAX_CHECKS), per-goal --max N.

The loop needs a persistent session (TUI or RPC). In print mode (pi -p) the goal directive and seat-durable state still apply, but check turns are not forced — print mode exits when the prompt pipeline completes, and a forced turn there races session teardown (measured).

State

Incarnation-durable state survives /new, /resume, and reloads within one process. This repository's native development installation stores it under .pi/state/goal/. The identity is the process-launch incarnation, never PI_SESSION_ID. Unknown-owner legacy goal-state.json files are quarantined rather than inherited. One active goal exists per incarnation; setting a new goal replaces that incarnation's goal.

State stores counters, bounded routing fields, and a SHA-256 evidence fingerprint. It does not persist free-form progress evidence.

Development installation

This directory is canonical source. Do not edit .pi/extensions/; generate that native-test installation with scripts/sync-dev-extensions.sh. The sync copies goal/ and its mosaic-core/lib/ dependency as ordinary files, verifies content hashes, and refuses to overwrite installation drift. It also enforces that goal/index.ts is the only extension entrypoint in this bounded package.

Start the isolated native test with bash scripts/goal-dev.sh. It runs the sync and passes the generated entrypoint explicitly to Pi while disabling ambient extension discovery. This does not install or reload the live fleet extension and does not add the extension to Docker.

Directory layout: index.ts for wiring, lib/ for logic, and test/ for hermetic checks. Pi supplies the extension API, TypeBox, and pi-ai imports at runtime.

Tests

node --test extensions/goal/test/*.test.ts
bash scripts/test-extension-package.sh

The suite includes pure state, persistence, incarnation fencing, exact report-only loop, duplicate evidence, legitimate wait, headless extension-runtime, and enforcement-neutralization red controls.

Bounded waits (operator opt-in)

Use /goal --wait-timeout 60 <goal text> to suspend automatic checks during a reported wait. The flag accepts 1086400 seconds and combines with --max N. Omitting it preserves the previous wait-loop behavior. Existing running sessions must /reload to load this patch; do not reload unrelated seats for a trial.

A valid goal_report with status: "in_progress", progress.kind: "wait", an owner, and a watch id or concrete nextCheck persists the deadline and yields. There are no model heartbeats during that wait. Repeated wait reports preserve its original deadline. A substantive progress report clears the wait. The extension neither evaluates arbitrary shell conditions nor sends messages to other agents: a relevant incoming message can start an ordinary turn, or the one-shot deadline asks the agent to reconcile the dependency once.

There is one automatic deadline wake per goal or explicit /goal resume. Reporting another wait after that wake pauses the goal, including when its wording changes. This intentionally limits the initial rollout; multiple successive timed dependencies require explicit resumption. Deadline expiry never supplies approval or permits the agent to invent missing input.

The timer belongs to this Pi process. Reload within the same incarnation restores the original deadline; shutdown cancels timers. A new process has a new fenced goal identity and does not inherit another incarnation's goal. This patch is not a process supervisor. Print mode records a manual wait and never schedules a wake. No external systemd timer is installed.

If the runtime is busy at expiry, readiness is checked in code every second for at most 60 seconds; this also covers manual compaction with no settle event. If still busy, the goal pauses with a UI notice. A dispatched deadline wake must appear in before_agent_start within 30 seconds or the goal pauses without resending. The matching event confirms turn start, not successful task execution. Stop, clear, replacement, satisfaction, and blocking cancel owned timers. Late reports cannot settle a paused goal. Reports also refuse if the goal changes while an asynchronous policy check is running. State snapshots use atomic file replacement; failed saves stop continuation instead of claiming a saved result.

The tests exercise unchanged waits, deadline dispatch and observation, unresolved pause, compaction, reload, cancellation, print mode, malformed persisted waits, and actual filesystem write failure. This is separate from live seat acceptance.