feat(extensions): establish canonical goal source (#54, #55)

This commit is contained in:
2026-09-06 02:32:32 -05:00
parent 44f257cb06
commit d4696d09eb
43 changed files with 6845 additions and 0 deletions
+129
View File
@@ -0,0 +1,129 @@
# 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.