@narumitw/pi-goal
v0.54.3
Published
Pi extension for autonomous single-objective /goal completion.
Downloads
49,870
Maintainers
Readme
🎯 pi-goal — Keep Pi Working Toward a Goal
Give Pi a session-scoped objective and let it continue from each fully settled idle boundary until the work completes, pauses, waits, or reaches a safety limit.
Goal mode adds explicit completion, blocker, and external-wait tools so managed execution stops for a clear reason instead of looping blindly.
✨ Features
- Starts or manages a session goal through one
/goalcommand and direct status, pause, resume, edit, or clear routes. - Continues exactly once from Pi's settled idle boundary after queued work, retries, and compaction have finished.
- Waits quietly for a follow-up when transient provider retries are exhausted instead of terminally blocking the Goal.
- Uses explicit
goal_complete,goal_blocked, andgoal_waittools with stale-goal guards and evidence requirements. - Tracks active, paused, blocked, usage-limited, budget-limited, waiting, and complete outcomes separately.
- Pauses after a configurable response limit or repeated no-progress runs and offers a guided review before continuing.
- Supports optional token budgets that stop Goal-owned work immediately when exhausted.
- Keeps Goal continuation accounting out of the leading system instructions so post-activation provider request prefixes remain stable.
- Persists the goal in the current session across reload, resume, compatible forks, and compaction.
- Rejects stale continuations and tool calls after replacement, pause, completion, limits, or other terminal state changes.
- Optionally exposes a default-off run protocol for trusted sibling extensions to start, observe, and cancel one Goal lifecycle.
📦 Install
Requires Pi 0.80.6 or newer for the agent_settled lifecycle event.
pi install npm:@narumitw/pi-goalTry without installing permanently:
pi -e npm:@narumitw/pi-goalBuild and try this package locally from the repository root:
npm --workspace @narumitw/pi-goal run build
pi -e ./packages/pi-goalThe package declares dist/index.ts, so an unbuilt local checkout must run the build before Pi loads the package directory.
🚀 Quick start
Run /goal <objective> to start working toward an objective, or run /goal to open the state-aware manager.
Use the manager to review status, pause, resume, edit, or clear the current goal.
⚙️ Settings
Settings are optional.
When ~/.pi/agent/pi-goal.json is absent, pi-goal uses these built-in defaults without creating the file:
{
"rpc": {
"enabled": false
},
"continuationLimits": {
"automaticTurns": 25,
"noProgressTurns": 3
}
}Use /goal → Settings… in the TUI to create or update the file interactively, or create and edit it directly.
The standard Settings screen keeps all three controls on one level in task order; the two safety limits open standard choice screens:
- Automatic-work limit shows the exact response limit or Unlimited. Choose Set response limit… to edit the current finite value (or the built-in default of 25 when switching from Unlimited), or choose Unlimited…. Unlimited requires confirmation that tool loops may continue consuming tokens and provider cost without a response-count cap.
- No-progress guard shows N runs or Off. Choose the default threshold, Off, or Set threshold… and enter a safe whole number greater than zero.
- Managed run RPC controls whether trusted installed extensions may start and cancel managed Goal runs. It defaults to Off and is a cooperation setting, not an extension security sandbox.
Custom number inputs reject zero, negative numbers, decimals, text, and unsafe integers without saving; use the explicit Unlimited or Off choice instead. Interactive changes are serialized, written atomically, preserve unknown fields, and apply to the current runtime. A successful change updates the visible state immediately. A failed save restores the prior value and reports the settings path so it can be retried. Escape returns to the previous screen without reverting changes that were already saved.
Pi-goal registers goal_complete, goal_blocked, and goal_wait once and keeps their schemas stable from startup.
Visible Goal tools do not mean Goal mode is active, and only the latest effective active Goal contract authorizes their use.
Pi-goal never widens a restrictive active-tool policy; activation rejects when required terminal tools are missing, and an active Goal pauses if they later disappear.
The retired toolVisibility key is ignored and preserved as unknown data when another setting is saved.
experimental.goals is a removed legacy setting.
If it remains true, pi-goal accepts the settings file, ignores the old queue feature, and shows an affected-user warning that recommends /goal edit when an active objective exists or /goal <objectives> when no active goal exists.
Later settings saves preserve unknown legacy fields instead of deleting them.
rpc.enabled accepts a boolean and defaults to false.
When disabled, a valid managed-run start receives RPC_DISABLED; manual and restored Goals remain unchanged.
A Settings-menu change applies immediately after its atomic save.
Disabling rejects new starts but lets an already accepted run continue publishing its exact state and accept its exact cancellation until terminal, avoiding stranded work.
Reload, replacement, and shutdown clear that in-memory ownership.
continuationLimits controls the runaway guards:
automaticTurnsaccepts a positive safe integer ornulland defaults to25. It counts every completed normalturn_endowned by automatically started Goal work, including model responses inside tool loops and matching Pi-owned retries. The user-triggered kickoff, resume, edit, and ordinary user runs are not charged. At the limit, the goal becomespausedwith causecontinuation_limit, pending continuation/recovery is cancelled, and the current operation is aborted before a 26th normal response starts. Pi may invoke a provider adapter once more with an already-aborted signal to produce its synthetic terminal event; that event is not counted and cannot resume Goal work. Set this field explicitly tonullto opt into Unlimited mode; existing explicitnullvalues remain compatible.noProgressTurnsis a positive safe integer and defaults to3. At the end of an automatic run, pi-goal compares visible assistant text after Unicode normalization, lowercasing, control-character removal, and whitespace collapse. Thinking and tool blocks are excluded; empty and punctuation-only output are equivalent. Consecutive empty or identical tool-free outputs increment the repeat count. Different non-empty output starts a new run at one, and any attempted tool call resets it. Set this field tonullto disable only this heuristic.
Settings are reread at Pi startup, session replacement, and /reload; direct external file edits are not watched live, while changes made through the Goal menu apply immediately.
A missing file remains absent and uses the built-in defaults.
The first successful settings change creates the file atomically; later saves preserve unknown fields.
Omitted fields use the defaults above.
Invalid or malformed existing settings are never overwritten; they produce a warning and fall back to all defaults.
In the TUI, Goal Settings becomes a read-only summary that identifies the invalid file and directs the user to fix it and run /reload.
Reload Pi after changing the file.
Plan mode or another restrictive policy may hide Goal tools.
Pi-goal does not fight that policy on restore or on later turns: activation rejects when required terminal tools are unavailable, and an already-active goal pauses without automatic continuation if they disappear.
A restrictive allowlist created before goal_wait existed can still run ordinary Goals with goal_complete and goal_blocked, but the model cannot enter external waiting until that allowlist also includes goal_wait.
The pause aborts a Goal-owned kickoff, resume, active-edit, or automatic-continuation prompt, but it does not cancel or stale-block an unrelated user or extension turn, including startup follow-ups after a restrictive restore.
🤝 Workflow coexistence
Goal is independently installable and keeps its standalone behavior when no other protocol participant is present.
On the characterized Pi 0.84.2 runtime, it participates in the anonymous workflow:mutex:v1 agent-workflow group.
An active Goal holds the group through ordinary work, external waiting, continuation delivery, compaction recovery, and provider retry.
Paused, blocked, usage-limited, budget-limited, complete, cleared, and legacy-queue-only states do not hold it.
Direct starts, menu starts, managed-run starts, stopped resumes, and stopped-goal edits use one final synchronous admission after validation and confirmation but before tool, Goal-state, persistence, prompt, queue, or status mutation.
Replacing or editing an already active or waiting Goal retains its current owner.
If admission is busy, TUI and RPC show an anonymous warning, print and JSON direct routes throw before mutation, and managed-run RPC emits one terminal ACTIVATION_FAILED event without creating a Goal.
Restored active Goal state acquires before tool restoration, persistence publication, status, wait timers, retry state, or continuation work. If restoration is busy, the Goal moves only to its existing paused safe state, does not change active tools or schedule work, and can be resumed explicitly after the other workflow ends. Restored stopped Goals and inert legacy queues do not acquire or schedule automatic work.
An active Goal still pauses if a non-participating restrictive policy later removes its required terminal tools.
The coexistence guarantee is cooperative and applies only when every contender implements v1 on the characterized Pi runtime and shares its event bus and session-manager identity.
A pre-v1, mixed-version, non-participating, forked, or otherwise uncharacterized counterpart remains unsupported for mutual exclusion.
Goal does not identify, inspect, configure, start, stop, or depend on another extension.
Guaranteed coexistence with Plan mode requires @narumitw/pi-plan-mode 0.52.0 or newer and this package at 0.53.0 or newer on the characterized Pi 0.84.2 runtime.
| Installation | Support |
| --- | --- |
| Goal without another workflow participant | Supported standalone behavior |
| Goal >=0.53.0 with Plan mode >=0.52.0 on Pi 0.84.2 | Workflow Mutex v1 coexistence guarantee |
| Either package below its floor, or another Pi runtime | Standalone behavior only; mutual exclusion unsupported |
💬 Commands
/goal
/goal status
/goal implement snake game
/goal --tokens 100k fix the failing test and verify it
/goal edit ship the smaller fix first
/goal pause
/goal resume
/goal clear- In the TUI,
/goalopens a standard state-aware manager. Its first action follows the current state: start when empty, pause when active, review a reached automatic-work limit, resume for other stopped states, or increase an exhausted token budget. Active and paused views show Automatic work: used of limit responses with the remaining count, or explicitly show Unlimited. A hard-cap pause opens Review and continue…, which states that objective, cumulative usage, and active time are preserved and previews that Continue resets the counter to zero and allows up to one more configured epoch. Change automatic-work limit… opens that setting while leaving the goal paused; Back and Escape make no change. Start with token budget… first offers25k, a suggested100k,300k, and Set a custom budget…, then collects the objective with the selected budget still visible. Custom input accepts examples such as300000,300k,2.5k, and1.5m; invalid input retains its draft for correction. Status, Settings, Help, invalid-settings guidance, Clear, and Close remain shallow, labeled routes. Arrow keys navigate, Enter selects or submits, Escape goes Back, and Ctrl+C closes the full flow. - In RPC mode, bare
/goaland/goal statusreport the current summary through an observable notification without opening terminal UI. Pi exposes no extension-command output channel in print or JSON mode, so those routes reject with an explicit unsupported-mode error instead of misreporting stderr as status output. - Menu-driven Replace and Clear actions preview the exact affected goal and require confirmation. Existing direct routes remain immediate for compatibility and automation.
/goal <goal_to_complete>starts goal mode. If another unfinished goal exists, Pi asks for confirmation before replacing it with a new active goal and resetting its usage counters. Failed kickoff delivery clears a new goal or restores the exact prior Goal and tool-policy snapshot; a previously active or waiting Goal keeps its existing workflow ownership./goal --tokens 100k <goal_to_complete>starts or replaces goal mode with a token budget.kandmsuffixes are accepted, for example100kor1.5m./goal edit <goal_to_complete>updates the existing goal objective without resetting usage counters. A successful active edit rotates the stale-turn guard and starts a fresh safety epoch. Paused, blocked, and usage-limited goals stay stopped and retain their safety state until resume. A budget-limited goal reactivates only whenedit --tokensraises its budget above current usage. Failed prompt delivery restores the exact previous Goal, guard id, safety counters, cause, tool-policy snapshot, and active ownership when applicable./goal pausestops prompt injection and auto-continuation, aborts the current turn, and keeps the goal for later resume. Only active goals can be paused./goal resumeresumes a paused, blocked, usage-limited, or budget-limited goal when its token budget allows it, rotates the stale-turn guard id, resets the automatic-response/repeat safety epoch when the queued resume prompt starts, clears a safety-pause cause, and reports the new finite epoch or explicit Unlimited state. Objective, cumulative usage, and elapsed time are preserved. If prompt delivery fails, the original stopped state, guard id, counters, fingerprint, and cause are restored./goal clearclears the current goal, status, pending continuation, inert legacy queue state, and legacy persisted state for the current working directory without aborting unrelated in-flight work.
The experimental ordered-goal queue has been removed.
Use /goal edit <objective> to reprioritize the active objective instead.
For example, if task b is complete and task c is in progress, edit the objective to say: task b is complete; do task a next; after task a, continue task c, then task d; do not redo task b unless verification shows it is incomplete.
Former queue command words such as add, prioritize, drop-last, skip, push, unshift, pop, and shift are ordinary objective text for unaffected users.
If a session still has legacy queue settings or persisted queue state, those words show a migration warning instead of replacing the active Goal.
Goal objectives are limited to 4,000 characters.
Put longer instructions in a file and reference the file path from /goal.
🛠️ Tools
goal_completerecords completion only for the exact active goal id and requires an evidence-based summary.goal_blockedrecords a true repeated impasse with the exact goal id, reason, evidence, and repeated-turn count.goal_waitpauses automatic continuation after the agent arranges an external wake source, with an optional bounded resume deadline.
🔁 Session and reload behavior
Goal state is stored as Pi session state, similar to Codex's thread-owned goals.
/reload and reopening the same Pi session can restore that session's unfinished goal.
An active restored goal already at or above its finite automatic-work limit pauses before another provider request and reports that progress is saved; use /goal to review and continue.
A restored waiting Goal remains quiet, excludes offline and waiting wall time from active elapsed time, and restores only its absolute optional deadline timer.
An active restored goal pauses when workflow admission is busy or either required terminal tool is missing, without changing the active tool set.
Active elapsed time is checkpointed before shutdown and restarted after reload only when the Goal is not waiting, so offline and stopped wall-clock time is excluded.
Automatic-response counts, repeat fingerprints, and safety-pause causes persist across reload and compaction.
A direct non-/goal user/RPC input resets the safety epoch only while the goal is active and reclassifies the in-flight run as manual; extension input and messages sent while stopped do not reset it.
Starting a new Pi session in the same working directory does not inherit the old goal.
The legacy { goal } shape remains valid, and missing safety fields normalize to zero/defaults.
Sessions created by the former standalone pi-goals experiment can still restore exactly one ordinary unfinished goal when no canonical goal-state entry exists.
If a session still contains old queue metadata, multiple legacy goals, a queued head, or a pending queue transition, pi-goal treats that state as inert legacy data.
It does not inject Goal prompts, advance the queue, or run any retained item automatically.
Affected users receive a warning that recommends starting one merged objective with /goal <objectives>, or using /goal clear to discard the old queue state.
Older versions wrote unfinished goals to ~/.pi/agent/pi-goal-state.json keyed by working directory.
This version no longer reads that global file, and /goal clear removes any legacy entry for the current working directory.
📊 Statusline states
pi-goal writes compact plain status strings for statusline extensions.
@narumitw/pi-statusline adds the default 🎯 icon unless configured otherwise:
active 3m · automatic 12/25— an active goal without a token budget; elapsed time counts only periods when its status is active and not waiting.waiting review monitor · automatic 12/25— an active Goal is quiet until non-Goal work or its optional deadline wakes it; the displayed reason is sanitized and bounded.active 18k/100k · automatic 12/25— an active goal with token usage and budget.active 3m · automatic Unlimited— explicit Unlimited automatic work.paused · automatic limit 25/25— the automatic-work limit paused the goal;/goalopens the recovery preview.paused · automatic 12/25— another pause reason stopped work while preserving the finite epoch.blocked · automatic 12/25— progress requires user or external action, or a terminal non-usage error stopped work.usage · automatic 12/25— the provider or account usage limit stopped work.budget 100k/100k · automatic 12/25— the user-configured token budget was reached; auto-continuation stops.complete— shown briefly aftergoal_completesucceeds.
💰 Token budgets and elapsed time
The TUI budget chooser describes token budgets as cumulative Goal usage, warns that the final model call may exceed the chosen value, and keeps the independent automatic-work response limit visible. It is not a dollar-cost cap. Choosing a preset or entering a custom value remains provisional until the objective is submitted; cancelling the chooser, custom input, or objective editor creates no Goal. Increase budget and resume… shows the exact current budget and usage, requires a new total above current usage, previews the new total plus automatic-work epoch, and resumes only after confirmation. If the goal or its usage changes while that dialog is open, no change is applied.
For each persisted assistant message, pi-goal uses finite, non-negative usage.totalTokens when available.
For compatibility with older or partial records, it otherwise sums finite, non-negative input + output + cacheRead + cacheWrite.
It does not add reasoning because reasoning is already part of output, or cacheWrite1h because that is a subset of cache writes.
Goal usage is the current branch's cumulative assistant total minus the baseline captured when the goal started, clamped at zero after branch rewinds.
Provider usage becomes authoritative only when an assistant message finishes, so a budget can overshoot by one model call.
When completed tool activity first exposes exhaustion, the goal transitions once to budget_limited, cancels continuation, recovery, waits, and stale Goal-owned work, aborts the current turn, and releases workflow ownership.
It does not queue a summary or another model turn after budget exhaustion.
Stale Goal tool calls remain blocked until an unrelated user or extension turn begins or the Goal is explicitly reactivated.
A budget-limited Goal cannot call goal_complete; raise its budget above current usage and resume or edit it first.
The default 25-response automatic-work limit is a response-count boundary, not a fixed cost ceiling: context size, cache pricing, output length, and provider rates vary, and the final capped response is still retained.
Pi derives displayed cost estimates from provider-reported token usage and local model pricing; pi-goal does not query a billing balance or enforce a dollar cap.
For tighter token control, choose a smaller automaticTurns value and/or use /goal --tokens; choosing Unlimited removes only the response-count boundary.
Elapsed time is accumulated only while status is active and the Goal is not waiting.
Waiting, paused, blocked, usage-limited, budget-limited, shutdown, and offline periods do not increase it.
Legacy session entries are migrated by preserving their accumulated seconds and starting a fresh active clock when loaded.
✅ How completion works
While a goal is active, Goal-owned messages carry persistence rules and a <goal_id> stale-turn guard, and pi-goal exposes goal_complete.
Kickoff, resume, edited-objective, wait-resume, and automatic-continuation prompts all place a trust boundary before the escaped objective, identifying it as user-provided task data; they preserve its full scope across turns and require the agent to derive concrete requirements from the objective and referenced artifacts.
They treat the current worktree, command output, tests, runtime behavior, PR state, rendered artifacts, and external state as authoritative; previous conversation and plans are context rather than proof.
Goal helper names, definitions, and active prompt metadata remain stable across Goal activation, continuation, token accounting, wait resume, completion, and clearing. Mode-only positive instructions live in the append-only active Goal contract instead of globally active tool prompt metadata. Current token-budget usage is carried by the newly appended Goal prompt instead of rewriting leading system instructions. The first accepted handoff for each Goal identity persists one deterministic hidden Goal contract at the same agent-start boundary, after previously retained conversation history. The contract explicitly supersedes earlier Goal contracts, excludes mutable token, iteration, and elapsed-time counters, and stays at its appended history position. Editing, replacing, and stopped-state resume append a new superseding active contract without deleting earlier provider input; failed handoff delivery appends no contract. Completion, clearing, and stopped transitions append one inactive superseding contract, while compaction and session restore append a missing current-state contract without waking a waiting Goal. These structural guarantees make provider prefix reuse possible, but the provider still decides cache eligibility, cache hits, pricing, and billing.
Before completion, the shared audit tells the agent to treat completion as unproven, inspect requirement-by-requirement evidence for every named artifact, command, test, gate, invariant, and deliverable, and match each check's scope to the requirement it supports.
Weak, indirect, missing, or merely consistent evidence means work must continue.
This prompt wording is a behavioral guardrail, not proof by itself: pi-goal can enforce the current goal id and reject empty or plainly contradictory summaries, but it cannot independently prove that external work is complete.
To finish, the agent must call goal_complete with the exact current goal_id and a summary of completion evidence.
Missing or stale goal_id values are rejected before summary validation.
Paused, blocked, usage-limited, and budget-limited goals cannot be completed until resumed.
The summary is completion evidence, not the stale-turn safety token.
If a turn ends before completion, pi-goal records usage and creates one continuation intent unless a circuit breaker pauses it first.
It dispatches that continuation only from Pi's agent_settled lifecycle after retries, automatic compaction, steering, and follow-up work have drained, ctx.isIdle() is true, and no messages are pending.
Repeated settled events cannot dispatch the same intent twice.
Goal-owned kickoff, resume, active-edit, and automatic-continuation deliveries are bound to the goal instance that created them; a delayed prompt from a replaced goal is aborted without rolling back, injecting, or stopping the newer goal.
Plain assistant text never marks a goal complete—even an exact-reply objective pauses safely when the model repeatedly omits goal_complete.
Manual compaction does not emit agent_settled, so its completion hook uses the same single-flight dispatcher as a narrow idle-only fallback.
Pi extensions cannot reserve an idle turn atomically like Codex core; another extension can still win the race after the idle check, and its newer turn supersedes the old continuation intent.
⏳ External waiting
Use goal_wait only after arranging a monitor or other wake source that will inject a non-Goal message when external state changes:
goal_wait({
goal_id: "<current-goal-id>",
reason: "Waiting for the review monitor",
resume_after_ms: 300000
})goal_id must match the current active Goal, reason must contain 1–1,000 characters, and the optional resume_after_ms must be a whole number from 1 through 2,147,483,647.
The deadline is a safety wake-up rather than a polling interval.
Requests below 10,000 milliseconds are accepted for compatibility but clamped to an effective 10,000-millisecond deadline; the tool result reports both the requested and effective values.
Prefer deadlines measured in minutes instead of repeated short wakes.
Omitting resume_after_ms intentionally permits an indefinite quiet wait.
An accepted call keeps the canonical Goal status active, checkpoints active elapsed time, cancels pending continuation work, persists the reason and absolute optional deadline, and terminates the normal single-tool run.
Call goal_wait alone because Pi only guarantees early termination when every finalized result in a parallel tool batch terminates.
When Pi exhausts retries for a transient provider error such as HTTP 429, pi-goal enters the same active waiting state without a deadline instead of marking the Goal blocked.
The warning reports bounded provider status and explains that a follow-up or /goal resume retries the Goal.
Context-overflow compaction exhaustion remains blocked because another model turn can repeat the same oversized request without corrective compaction.
Interactive input, RPC input, another extension's sendUserMessage() input, and supported non-Goal custom follow-ups clear the wait before their turn runs.
pi-goal-owned kickoff, resume, edit, continuation, stale, or cancelled prompts do not count as external wake-ups.
Pi does not expose the sending extension's identity, so any non-Goal extension message is treated as a wake signal.
After a waking turn ends, ordinary continuation rules apply again.
The agent can complete or block the Goal, continue working, or call goal_wait again after arranging the next wake source.
/goal resume also clears waiting and sends one manual resume prompt without resetting cumulative usage or the safety epoch.
/goal pause, clear, edit, replace, completion, blocking, terminal limits, tool loss, session replacement, and shutdown cancel the in-memory deadline owner.
A future deadline is restored from its absolute timestamp after reload. Reload never restarts, extends, or newly clamps an already-persisted absolute deadline, including a short deadline written by an older version. An already-due deadline waits for Pi's settled, idle, no-pending-message boundary and then requests exactly one continuation through the normal dispatcher. If that delivery throws, pi-goal restores the wait, retries once after one second, and leaves the Goal visibly waiting after a second failure instead of retry-looping. A deadline never sends a prompt directly from a stale timer.
Waiting time is excluded from Active elapsed, while tokens, iteration, automatic-response count, no-progress state, and managed-run ownership remain preserved.
The managed-run protocol continues reporting active without a duplicate state event because waiting is non-terminal, including after transient provider retry exhaustion.
Editing or replacing a waiting Goal clears the previous wait so the updated objective performs a fresh external-state check.
🚧 Blocked goals
goal_blocked is intentionally narrower than completion or ordinary clarification.
Every goal-mode prompt repeats the blocked audit: the model must provide the exact current goal_id, a specific reason describing the user or external action required (up to 1,000 characters), concrete evidence from the failed resolution attempts (up to 4,000 characters), and repeated_turns showing the same blocker recurred for at least three consecutive goal turns.
A resumed goal starts a fresh blocker audit.
Empty or oversized reasons/evidence, stale ids, non-whole turn counts, stopped goals, and fewer than three turns are rejected.
Accepted blocker reports set blocked, stop automatic continuation, and terminate the tool batch when Pi can do so safely.
Do not use goal_blocked merely because work is difficult, incomplete, uncertain, awaiting normal clarification, or affected by a recoverable tool/provider failure.
The user can resolve the external condition and run /goal resume to rotate the goal id and continue.
🛑 Interruption and queued-input behavior
A user pause or aborted turn produces paused; a terminal provider/account quota error produces usage_limited; another non-retryable agent error produces blocked.
Each stopped transition cancels pending continuation intent or delivery, aborts stale work when applicable, and blocks stale tool calls until the next non-goal user prompt, successful reactivation/replacement, or /goal clear.
On /goal clear, the extension clears goal state, continuation markers, and any stale tool-call block without aborting an unrelated in-flight turn.
Retryable provider interruptions and overflow compaction retries stay active while Pi retries; no extra continuation is queued, and automatic ownership remains charged through retry agent_start events.
If matching provider recovery still exists at agent_settled, retries are exhausted and the Goal enters a deadline-free active wait before any continuation dispatches.
A later non-Goal input wakes the same Goal without rotating its stale-turn guard, so the model can continue, complete, or enter another wait with the current goal_id.
If matching compaction recovery still exists at agent_settled, the Goal becomes blocked because recovery did not produce usable context.
Stale recovery cannot wait or block a replacement goal.
User and extension work that starts before settlement supersedes the older continuation intent, and pending messages always take priority.
🤝 Managed run RPC
With rpc.enabled: true, pi-goal exposes a session-local, dependency-free protocol over Pi's shared pi.events bus.
It is intended for trusted sibling extensions that need to start, observe, and cancel one Goal lifecycle without driving the /goal command.
Installed Pi extensions remain fully privileged: this setting controls only whether pi-goal cooperates with these channels and is not authentication or sandboxing.
The public channels are:
pi-goal:start
pi-goal:cancel
pi-goal:event:${runId}The protocol intentionally has no separate version field or versioned channel namespace.
Before starting, the caller must generate a session-unique runId, subscribe to its event channel, and then emit:
pi.events.emit("pi-goal:start", {
runId: "consumer-generated-run-id",
objective: "Ship and verify the feature",
tokenBudget: 100000, // optional positive integer
});runId must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$; UUIDs are recommended.
It is a correlation identifier, not a secret or authenticated caller identity.
The objective uses the same 4,000-character validation as /goal, and tokenBudget is an absolute positive integer rather than a k/m string.
A successful start produces canonical state on pi-goal:event:${runId}:
{
"type": "state",
"runId": "consumer-generated-run-id",
"goalId": "<pi-goal-instance-id>",
"status": "active"
}Subsequent state events use active, complete, blocked, paused, usage_limited, budget_limited, or cleared.
complete is the only successful terminal outcome.
A matching completion may include summary; other terminal outcomes may include reason.
Events come only from canonical Goal persistence and only for the matching managed run.
Manual and restored Goals are not adopted or broadcast, unchanged persistence does not duplicate a status, and each run emits at most one terminal event.
Terminal events are dispatched after the underlying Goal transition settles, so a listener can start the next managed run directly after complete without re-entering completion cleanup.
Other terminal statuses leave a stopped Goal that must be resolved or cleared first.
If a manual edit, replacement, or edit transition rotates the Goal id, the prior managed run ends as cleared with a superseded reason; the replacement remains outside that run.
To cancel before or after activation, emit the same runId:
pi.events.emit("pi-goal:cancel", {
runId: "consumer-generated-run-id",
reason: "Parent work was cancelled", // optional, at most 1,000 characters
});Cancellation uses the normal Goal pause transition.
It cannot affect a manual, restored, stale, or different run.
The resulting paused state is the cancellation result; there is no separate reply envelope.
A caller must not reopen a terminal runId, and a later manual /goal resume is outside that completed managed run.
Rejected operations emit a structured error on the same run event channel:
{
"type": "error",
"runId": "consumer-generated-run-id",
"operation": "start",
"error": {
"code": "RPC_DISABLED",
"message": "Managed run RPC is disabled."
}
}Stable codes are RPC_DISABLED, INVALID_REQUEST, NO_ACTIVE_SESSION, RUN_ID_IN_USE, RUN_NOT_FOUND, GOAL_ALREADY_EXISTS, ACTIVATION_FAILED, and SUPERSEDED.
Consumers branch on code; message is diagnostic.
An unsafe or missing runId is ignored because there is no safe response channel.
Start never prompts for replacement: any pre-existing Goal is rejected.
The protocol binds only after current settings and restored Goal state load, and unbinds before session shutdown.
A caller must not assume that emit() waits for Goal completion; it should wait for a terminal run event and participate in its own session-shutdown cleanup.
This breaking contract replaces and removes pi-goal:rpc:start, pi-goal:rpc:pause, request-scoped start replies, and the global pi-goal:state broadcast.
No compatibility aliases are registered.
🧠 Use cases
- Finish implementation tasks without stopping at a plan.
- Keep debugging until the bug is verified fixed.
- Run refactors that require multiple tool cycles.
- Encourage agents to test, lint, or typecheck before completion.
- Make long-running Pi coding sessions more autonomous.
🗂️ Package layout
packages/pi-goal/
├── dist/ # Generated TypeScript runtime loaded by Jiti
├── scripts/
│ └── build-runtime.mjs # Deterministic runtime builder and boundary validator
├── src/
│ ├── index.ts # Pi package entrypoint
│ ├── goal.ts # Order-explicit extension composition root
│ ├── command-registration.ts # Lightweight slash-command adapter with lazy manager/settings UI
│ ├── commands.ts # Per-factory user-command controller
│ ├── tools.ts # Goal completion and blocker tool adapters
│ ├── lifecycle.ts # Pi session, agent, tool, and compaction event adapter
│ ├── goal-contract.ts # Deterministic post-compaction model contract
│ ├── runtime.ts # Per-factory Goal state, transitions, prompts, and budgets
│ ├── tool-policy.ts # Goal tool names and read-only availability checks
│ ├── safety.ts # Output normalization and no-progress fingerprint state
│ ├── wait.ts # External-wait validation and session timer ownership
│ ├── errors.ts # Pi-aligned provider error and retry classification
│ ├── markers.ts # Bounded Goal prompt marker parsing and formatting
│ ├── run-protocol.ts # Default-off managed-run protocol and session ownership
│ └── *.ts # Package-local parsing, settings, prompts, accounting, and persistence
├── README.md
├── LICENSE
├── tsconfig.json
└── package.jsonindex.ts is the Pi entrypoint and forwards to goal.ts; the other source modules are internal.
The package exposes its Pi extension through package.json:
{
"pi": {
"extensions": ["./dist/index.ts"]
}
}The generated runtime is built from the authoritative src/index.ts graph and does not import back into src.
🔎 Keywords
Pi extension, Pi coding agent, goal mode, autonomous coding agent, AI agent workflow, task completion, agent loop, verification, TypeScript Pi package.
📄 License
MIT.
See LICENSE.
