146 lines
8.3 KiB
Markdown
146 lines
8.3 KiB
Markdown
# 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 10–86400 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.
|