---
slug: "pi-goal-expander"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/clankercode/pi-goal-expander@master/README.md"
repo: "https://github.com/clankercode/pi-goal-expander"
source_file: "README.md"
branch: "master"
---
# pi-goal-expander

Grok-style **goal harness** for [Pi](https://pi.dev): expand a rough objective into a frozen plan, drive multi-turn pursuit, and **harness-own verification** so the model cannot self-certify.

Optional soft integration with [`@clanker-code/pi-subagents`](https://github.com/clankercode/pi-subagents) / upstream for planner/skeptic roles (degrades without it).

## Install

```bash
pi install npm:pi-goal-expander
# local:
# pi install /path/to/pi-goal-expander
# pi install git:github.com/clankercode/pi-goal-expander
```

Smoke:

```bash
pi -ne -e ./src/index.ts
```

## Surfaces

| Surface | Purpose |
|---------|---------|
| `/goal <objective>` | Start harness (research → expand plan → execute) |
| `/goal-expand <text>` / `/goal expand <text>` | Same as `/goal` (starts harness) |
| `/goal status \| pause \| resume \| clear \| help` | Lifecycle + help |
| `update_goal` tool | `message` / `completed` / `blocked_reason` |
| `goal_expand` tool | Same as `/goal` for the model |

## How it works

1. **Start** — create goal state under `.pi/goal/<id>/`, inject a slim **goal_set** notice while the planner runs, then write `plan.md` (planner subagent if available, else structural draft on disk only). When pi-subagents is available, the planner spawn sets **`inheritContext: true`** (session fork / parent conversation inheritance) and also hands off a compact parent-branch transcript into the planner prompt when `sessionManager.getBranch()` is available — so the planner sees prior chat/research, not only the objective string.
2. **Execute kickoff** — short message with objective + plan/evidence paths + steps (`triggerTurn: true`). The full plan body stays **on disk**; kickoff does **not** paste the draft skeleton into the transcript
3. **Execute** — agent reads/rewrites `plan.md` as needed, works the checklist, keeps `evidence/index.md` filled
4. **Verify** — `update_goal({ completed: true })` runs pre-verify + evidence-index gate (bias: not achieved without proof), then a **blocking** skeptic panel when pi-subagents is available (spawn → wait → aggregate **before** Achieved)
5. **Continue / stall / complete** — gaps replayed; same gap fingerprint twice → pause; verify cap → pause; success → receipt + user-visible completion summary (reload hint)

Statusline: `◎ plan` · `◎ plan <agentId>` (while planning) · `● active` · `🔬 verify` · `⏸ pause` · `✓ done`

### Progress milestones

While executing, call `update_goal({ message })` at least for:

- `researched` — codebase/context research done
- `implementing` — implementation underway
- `pre-complete` — work done, about to request verify

Optional mid-checklist notes are fine. History stores these as typed `progress` events.

## Lifecycle events

The harness emits custom session messages (`customType: pi-goal-expander:event`, usually `display: false`) with a `details.kind` field. Useful for dogfood logs and dashboards.

| `kind` | When |
|--------|------|
| `session_restore` | Active goal reloaded on `session_start` |
| `goal_set` | Goal created; planner may still be running (display message) |
| `planning_started` | Phase → planning |
| `planning_completed` | Plan written; phase → executing |
| `planning_failed` | Planner hard-failed |
| `goal_start` | Execute kickoff injected |
| `progress` | `update_goal({ message })` logged |
| `verify_started` | Verify round begins (may re-emit with panel `agentIds`) |
| `verify_not_achieved` | Verify failed; gaps recorded |
| `verify_achieved` | Verify passed |
| `goal_completed` | Goal status complete (after verify_achieved) |
| `verify_paused` | Verify cap hit → pause |
| `paused` / `resumed` / `cleared` | `/goal pause\|resume\|clear` |
| `blocked_attempt` | `update_goal({ blocked_reason })` |
| `goal_complete_summary` | User-facing completion blurb (`pi-goal-expander:complete`, display true) |

## Verify policy (goal kind → skeptic N)

Panel size is derived from `plan.md` **Goal kind** (see `src/loop/verify-policy.ts`):

| Goal kind | Default skeptic N |
|-----------|-------------------|
| `analysis` / `research` | **1** |
| `code-change` | **3** |
| missing / unknown | config `skepticN` (default **3**, clamp 1–5) |

Config `skepticN` is the fallback when kind is absent, not a hard override of kind defaults. Kind is read from the `## Goal kind` section of the active plan at `completed:true` time.

### Panel timeout / infra policy

| Condition | Outcome |
|-----------|---------|
| No event bus / pi-subagents ping fails | **Degraded path** — Achieved only if preverify + evidence gates pass (no false “panel passed”) |
| Bus alive but all spawns fail, all waits timeout, or no usable verdict text | **Fail-closed** — `NotAchieved` with gap `verifier panel unavailable` |
| Partial verdicts | Aggregate what arrived (`parseVerifierVerdict` + `aggregateVerdicts`, bias-to-refute; malformed/empty = refute) |

Per-skeptic wait uses config `subagentsTimeoutMs` (default 600s). The panel is on the critical path: `verify_achieved` is not emitted until wait+aggregate finish or the fail-closed policy applies.

### Planner timeout / degrade policy

| Condition | Outcome |
|-----------|---------|
| No bus / pi-subagents ping fails | **Degraded** structural draft (when soft mode); kickoff notes the real reason (`ping_failed` / unavailable) |
| Planner spawned but **wait times out** | **Fail-closed** — `planning_failed`, goal `user_paused`; planner agent **stopped**; **no** execute kickoff; **no** draft baseline as “success” |
| Structural draft write | Never overwrites a non-draft `plan.md` already on disk |

Planner wait uses config `plannerTimeoutMs` (default **30m**), separate from panel `subagentsTimeoutMs` (default 10m). Lifecycle events `planning_completed` / `planning_failed` include `plannerReason` (`ok` \| `no_bus` \| `ping_failed` \| `wait_timeout` \| `spawn_error` \| `agent_failed` \| `structural_fallback`).

### Completion UX

On Achieved the harness emits a user-visible notify + display summary (`pi-goal-expander:complete`) with objective, receipt path (when receipts enabled), and a generic reload hint (`/reload` if UI settings look stale). That is the last **goal-owned** UI action for the goal; further continuation / next-step injects for that `goalId` are suppressed. Other extensions may still emit after; the goal harness itself stays quiet.

## Goal state location vs edit scope

| What | Where |
|------|--------|
| Goal store (`plan.md`, `state.json`, `evidence/`, receipts) | `<cwd>/.pi/goal/<id>/` by default |
| Override store root | `PI_GOAL_ROOT` (absolute path used as the goal root) |
| Override cwd used for store resolution | `PI_GOAL_CWD` (falls back to extension `ctx.cwd`, then `process.cwd()`) |

**Edit scope is not limited to the goal store.** Objectives often name files outside the session cwd (e.g. `~/.pi/agent/settings.json`). Agents should treat `.pi/goal/` as harness bookkeeping only; allowed write targets follow the objective.

## Evidence

Before completion succeeds, write:

`.pi/goal/<id>/evidence/index.md`

```markdown
| Criterion | Artifact | Notes |
|-----------|----------|-------|
| 1 | tests/foo.test.ts | … |
| 2 | json:path/to/settings.json#powerline.cost.subscriptionDisplay=reported-cost | config assert |
```

**Artifact forms**

- **Path** — any path-like string is checked for existence (`tests/…`, `src/…`, absolute paths).
- **JSON assert** — `json:<filePath>#<dotted.path>=<expected>` reads the file, walks the dotted path, and requires an exact value match. Completion fails if the assertion fails even when the file exists (useful for config/settings goals).

**Draft plan gate:** `update_goal(completed:true)` returns NotAchieved with `plan not expanded beyond draft` if `plan.md` still matches the structural expand skeleton. Rewrite the plan with real criteria before completing.

## Development

```bash
npm install
just check
```

**Harness contract test** (`tests/harness-contract.test.ts`): memory bus + `installMockSubagentsRpc` asserts the dogfood event order `goal_set` → planning events → `goal_start` → `verify_started` → complete (no live LLM).

## Release

See [RELEASE.md](https://github.com/clankercode/pi-goal-expander/blob/HEAD/RELEASE.md):

```bash
just release patch   # or minor | major | 0.2.0
```

## Design

[docs/superpowers/specs/2026-07-20-goal-harness-design.md](https://github.com/clankercode/pi-goal-expander/blob/HEAD/docs/superpowers/specs/2026-07-20-goal-harness-design.md)

## License

**Unlicense** (public domain).
