Files
stack/extensions/goal/README.md
T

146 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 reconcile a paused or waiting goal (kicks one 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 with ready work and no unresolved wait, 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.
## Quiet waits and optional deadlines
Every accepted wait suspends automatic checks, including untimed waits. No
cooldown or model polling is used. A next-check condition describes the dependency;
it is not executed as code, and the extension does not register a watch for it.
Existing external message/watch delivery or explicit `/goal resume` can start
reconciliation. Incoming messages alone do not clear the wait. The model must
verify the dependency and report substantive progress or completion. Unrelated
input preserves the wait. Explicit resume clears the old wait; another unresolved
wait report suspends checks again. Paused and blocked goals still require explicit
operator resume, not merely an incoming message.
Use `/goal --wait-timeout 60 <goal text>` to add one deadline wake to a wait.
The flag accepts 1086400 seconds and combines with `--max N`. Without it,
no deadline timer is armed. Existing running sessions require a controlled reload
after an approved deployment; 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 wait and yields.
A deadline is persisted only when the operator configured one.
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.
An in-memory pending-check guard prevents duplicate settle/startup events from
queuing the same check before its matching start is observed. It does not cancel
messages already queued by Pi, span independent extension instances, or supply a
process supervisor. Wait state itself persists across same-incarnation reloads.
The tests exercise timed and untimed 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.