goal — Run the Agent to Verifiable Completion
Lead
/goal stops the agent from halting at a plan or partial work: after you set a goal it auto-continues from the idle boundary until completion is proven with evidence, a true blocker hits, or the token budget runs out.
Overview
Long coding-agent tasks fail two ways: they stop at a plan or half-finished work waiting for confirmation (premature yield), or they claim done without verification (false completion). goal mode guards both — it forces continuation until verifiable completion, and requires the completion call to carry the current goal_id and a summary of completion evidence.
Pi has no built-in autonomous completion loop. @piex-dev/goal adds one as a pure extension: it registers a /goal command and two terminal tools (goal_complete / goal_blocked), and dispatches continuation from the fully-settled idle boundary via pi's agent_settled lifecycle — only after retries, compaction, steering, and follow-ups have drained, with no duplicate dispatch.
How it works
The agent_settled continuation model
Continuation is not dispatched immediately at agent_end. After an agent turn, pi may still be retrying, auto-compacting, steering, or queuing follow-ups; firing continuation then would collide with that work. goal records the continuation intent as a ticket and dispatches it once at agent_settled (agent truly idle, no pending message). Repeated settled events cannot dispatch the same intent twice. Manual compaction does not emit agent_settled, so the compaction hook reuses the same single-flight dispatcher as a narrow fallback.
Stale-continuation guard: owned-prompt markers
Goal-owned prompts (kickoff, resume, continuation) inject a marker comment. If a delayed old prompt arrives after the goal is replaced or paused, the marker match is consumed and fires no model turn, so an old goal's continuation cannot override a newer goal. A stale tool-call block also intercepts stale tool calls after a goal stops, until fresh non-goal user input or a successful resume.
The goal_id stale guard
goal_complete / goal_blocked must carry the current goal_id and are rejected on mismatch. resume / edit rotate the goal_id so a delayed turn from a previous generation cannot complete the newer goal. This is the second line of defense beyond owned-prompt markers, blocking cross-generation false completion.
The blocked channel: real impasses need evidence
goal_blocked is not an ordinary pause. It requires the current goal_id, a specific reason, concrete evidence, and the same blocker recurring for at least three consecutive goal turns (repeated_turns ≥ 3). Empty reason/evidence, stale id, non-integer, or fewer than three turns are all rejected. Its value is not just the tool itself — the audit wording is repeated in every goal prompt, continually shaping a "don't yield lightly, impasses need evidence" behavior even when the tool is never called.
Accounting: cumulative totalTokens − baseline
For each persisted assistant message it uses usage.totalTokens (falling back to input + output + cacheRead + cacheWrite when unavailable). Goal usage = the current session branch's cumulative assistant total minus the baseline captured at start, clamped to zero after a rewind. Provider usage is authoritative only when an assistant message finishes, so a budget can overshoot by one model call.
Budget wrap-up
When exhaustion is first exposed after completed tool activity, the goal transitions once to budget_limited, cancels continuation, and queues one bounded wrap-up instruction: summarize progress/results/blockers only, no substantive tools. A tool-seeking model that calls a substantive tool during wrap-up is aborted to prevent an unbounded loop. A rejected goal_complete also terminates the wrap-up; an accepted completion still requires existing evidence proving every requirement — budget exhaustion itself is never completion.
Usage
Install
pi install npm:@piex-dev/goalSource: extensions/goal
Commands
/goal # show current goal, status, turns, time, tokens
/goal implement snake game # start goal mode
/goal --tokens 100k fix tests # start with a token budget
/goal edit ship smaller fix # change the objective, keep counters
/goal pause # pause continuation
/goal resume # resume
/goal clear # clear the goal--tokens accepts k/m suffixes (100k, 1.5m). Objective text is capped at 4,000 characters; for longer instructions put them in a file and reference the path from /goal.
Verification flow
1. /goal <objective> starts; agent gets the kickoff prompt and goal_id
2. agent works autonomously; if incomplete at turn end, agent_settled auto-continues
3. on completion call goal_complete({ goal_id, summary }); summary is audited
4. on a real impasse call goal_blocked({ goal_id, reason, evidence, repeated_turns })Configuration
{
"toolVisibility": "always"
}Path ~/.pi/piex-dev/goal/goal.json. toolVisibility: "always" (default, tools stay in the schema) or "after-first-goal" (revealed only after the first activation or an unfinished-goal restore, keeping non-goal sessions prompt-cache stable). Invalid config falls back to defaults with a warning; the extension never creates the file.
Implementation
Ported from @narumitw/pi-goal (pi-extensions) as a baseline rewrite, not a reference.
| Module | Responsibility |
|---|---|
| goal.ts | entry: tool definitions + /goal command + lifecycle hook orchestration |
| runtime.ts | per-session state, accounting, continuation dispatch, prompt ownership, tool policy |
| persistence.ts | ActiveGoal type + session-entry serialize/load |
| settings.ts | read ~/.pi/piex-dev/goal/goal.json |
| prompts.ts | inline prompt builders (trust boundary + completion audit + continuation) |
Trimmed
| Removed | Reason |
|---|---|
| Experimental ordered queue (add/prioritize/drop-last/skip) | pi-goal itself marks it experimental; not in v1 |
| Cross-extension RPC contract (rpc:start/pause, state events) | piex has no subagent extension consumer yet — YAGNI |
| /guided-goal LLM interview | couples oh-my-pi internals; it is objective refinement, not completion; deferred to a later prompt package |
piex-ified
Package name @piex-dev/goal (drops the pi- prefix, matching hashline/plan); config lives under ~/.pi/piex-dev/goal/; double quotes + semicolons, node: prefix, relative imports .js; does not read the upstream ~/.pi/agent/pi-goal-state.json (new package, no legacy users, clean slate).
Design notes
| Project | Mechanism | piex decision |
|---|---|---|
| pi-extensions/pi-goal | pure pi extension: two tools + agent_settled continuation + goal_id guard + status taxonomy + recovery | adopted: extension mechanism, two tools, continuation model, state machine ported wholesale as the baseline |
| oh-my-pi | goal built into the coding-agent core (ToolSession/obfuscator/framedBlock/provider transport) | not adopted: cannot run without the core, violating piex's "100% Extension API" constraint. Borrowed its completion-audit prompt wording and token-delta accounting idea |
Key tradeoff: an autonomous loop's worst failures are false completion and infinite spinning, so the schema-level enforcement of goal_id guard, summary contradiction check, and blocked evidence threshold are all kept; a single op tool is simpler but leaves these three bare, so it is not chosen.
Changelog
Versions
| Version | Date | Changes |
|---|---|---|
| 0.1.0 | 2026-07-19 | Initial release: single-goal lifecycle + agent_settled continuation + token budget + wrap-up; two tools (goal_complete/goal_blocked) + goal_id guard; experimental queue and RPC contract trimmed |
Roadmap
| Direction | Plan |
|---|---|
| Evaluation | compare single op vs two tools on false-completion / spin rates; let data decide whether to simplify |
| Ordered queue | add experimental add/prioritize/drop-last/skip on demand |
| RPC contract | add rpc:start/pause + state events once a subagent extension consumer exists |
| guided-goal | introduce objective-refinement interview as a separate prompt package |
Source Markdown: docs/packages/goal.md