pi-goala 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 (2026-07-26)
4
+
5
+ - Document how `AGENTS.md` and Pi skills customise project and personal
6
+ workflows around Goala's lifecycle.
7
+ - Describe planning, execution, and verification models as roles rather than
8
+ incorrectly presenting the repair loop as a separate phase.
9
+ - Clarify the fallback executor activation threshold and distinguish it from
10
+ unavailable-model fallback.
11
+ - Add direct and extension-level routing tests for every model role, including
12
+ the fallback activation boundary.
13
+
3
14
  ## 0.3.1 (2026-07-26)
4
15
 
5
- - Make each phase default to keeping its current model configuration.
16
+ - Make each role default to keeping its current model configuration.
6
17
  - Separate reasoning-effort changes from provider/model changes.
7
18
  - Select provider, then a filtered model, while skipping provider selection
8
19
  when only one authenticated provider is available.
@@ -10,7 +21,7 @@
10
21
 
11
22
  ## 0.3.0 (2026-07-26)
12
23
 
13
- - Add interactive per-phase provider, model, and reasoning selection from Pi's
24
+ - Add interactive per-role provider, model, and reasoning selection from Pi's
14
25
  authenticated available-model registry.
15
26
  - Support mixed-provider planner, executor, step-verifier, final-verifier, and
16
27
  repeated-repair roles.
@@ -69,7 +80,7 @@
69
80
 
70
81
  ## 0.1.0
71
82
 
72
- - Add persistent goal, plan, execution, verification, and bounded repair phases.
83
+ - Add persistent goal, plan, execution, verification, and bounded repair loops.
73
84
  - Add configurable planner, executor, verifier, and repair model roles.
74
85
  - Add fresh planning/execution sessions and logical verifier context isolation.
75
86
  - Add verifier-gated local episodic memory with SQLite FTS5 retrieval.
package/README.md CHANGED
@@ -39,7 +39,7 @@ pi install npm:pi-goala
39
39
  Pin a release for reproducible team installations:
40
40
 
41
41
  ```text
42
- pi install npm:pi-goala@0.3.1
42
+ pi install npm:pi-goala@0.3.2
43
43
  ```
44
44
 
45
45
  Pi's package command both downloads Goala and registers its extension. A plain
@@ -49,7 +49,7 @@ supported installation path.
49
49
  From GitHub or a local checkout:
50
50
 
51
51
  ```text
52
- pi install git:github.com/barryking/pi-goala@v0.3.1
52
+ pi install git:github.com/barryking/pi-goala@v0.3.2
53
53
  pi install /absolute/path/to/pi-goala
54
54
  ```
55
55
 
@@ -317,6 +317,7 @@ Use this hierarchy:
317
317
  | Detailed architecture, domain rules, and decisions | Versioned repository docs, linked from `AGENTS.md` |
318
318
  | Product direction and future milestones | `PROJECT_GOAL.md` or `ROADMAP.md` |
319
319
  | Personal defaults that apply to every repository | `~/.pi/agent/AGENTS.md` |
320
+ | Repeatable procedures such as commit/PR, release, deployment, or framework workflows | Pi skills, referenced from `AGENTS.md` when mandatory |
320
321
  | Distilled experience from successfully completed work | Verified Goala episodic memory |
321
322
 
322
323
  Pi loads repository and global `AGENTS.md` files into every fresh Goala phase.
@@ -357,6 +358,22 @@ can include it as an evidence-backed episode finding. Important decisions
357
358
  should still be committed to the repository; future memory is bounded,
358
359
  relevance-ranked, and deliberately treated as untrusted evidence.
359
360
 
361
+ ### Customise your workflow
362
+
363
+ Goala works with a standard Pi installation and provides the goal, planning,
364
+ execution, verification, and memory lifecycle. It does not prescribe every
365
+ project procedure.
366
+
367
+ Put concise, always-applicable rules in the repository's `AGENTS.md`, and put
368
+ personal defaults that apply across repositories in
369
+ `~/.pi/agent/AGENTS.md`. Use Pi skills for detailed, repeatable procedures such
370
+ as commit and pull-request handling, releases, deployments, migrations, or
371
+ framework-specific workflows.
372
+
373
+ When a skill is mandatory for particular work, reference it from `AGENTS.md`.
374
+ This keeps the instructions loaded into every phase short while allowing the
375
+ full procedure to be loaded only when relevant.
376
+
360
377
  ### When direction changes
361
378
 
362
379
  Use `/goal revise <feedback>` when the current checkpoint needs rework. Use
@@ -370,13 +387,17 @@ new bounded goal when the desired outcome has materially changed.
370
387
 
371
388
  The recommended OpenAI Codex preset is:
372
389
 
373
- | Phase | Model | Reasoning |
390
+ | Model role | Model | Reasoning |
374
391
  |---|---|---:|
375
- | Plan | `gpt-5.6-sol` | medium |
376
- | Execute | `gpt-5.6-luna` | medium |
377
- | Optional step verification | `gpt-5.6-luna` | medium |
378
- | Verify | `gpt-5.6-sol` | medium |
379
- | Repeated repair | `gpt-5.6-terra` | medium |
392
+ | Planner | `gpt-5.6-sol` | medium |
393
+ | Executor | `gpt-5.6-luna` | medium |
394
+ | Optional step verifier | `gpt-5.6-luna` | medium |
395
+ | Final verifier | `gpt-5.6-sol` | medium |
396
+ | Fallback executor | `gpt-5.6-terra` | medium |
397
+
398
+ Repeated repair is not a separate lifecycle phase. It loops back into
399
+ execution after verification fails. The fallback executor replaces the normal
400
+ executor only after the configured number of failed verification attempts.
380
401
 
381
402
  If those models are unavailable, Goala can use the model that was active
382
403
  when Pi started. Run `/goala-setup current` to persist that portable
@@ -3,7 +3,8 @@
3
3
  Goala is one Pi extension with three responsibilities:
4
4
 
5
5
  1. persist a goal and its acceptance contract;
6
- 2. route work through plan, execute, verify, and repair phases;
6
+ 2. route work through planning, execution, independent verification, and a
7
+ bounded repair loop;
7
8
  3. retrieve and promote bounded verified memory.
8
9
 
9
10
  ## Module boundaries
@@ -13,7 +14,7 @@ own one concern:
13
14
 
14
15
  | Module | Responsibility |
15
16
  |---|---|
16
- | `index.ts` | Commands, phase transitions, model routing, and event orchestration |
17
+ | `index.ts` | Commands, phase transitions, and event orchestration |
17
18
  | `tools.ts` | Tool schemas and state transitions caused by tool submissions |
18
19
  | `session.ts` | Fresh-session handoff and logical phase context slicing |
19
20
  | `sources.ts` | Safe project-local source resolution, hashing, and drift detection |
@@ -23,6 +24,7 @@ own one concern:
23
24
  | `workflow.ts` | Goal-state types, normalization, and invariants |
24
25
  | `memory.ts` | Verified episodic storage and retrieval |
25
26
  | `recovery.ts` | Discovery of unfinished goals in saved Pi sessions |
27
+ | `routing.ts` | Model-role selection and fallback-executor activation |
26
28
  | `setup.ts` | Interactive available-model selection for each lifecycle role |
27
29
  | `config.ts` | Namespaced configuration and model-role presets |
28
30
 
@@ -105,6 +105,13 @@ unchanged.
105
105
  }
106
106
  ```
107
107
 
108
+ `fallbackExecutor` is not a separate lifecycle phase. It replaces the normal
109
+ executor inside the repair loop after repeated verification failures.
110
+ `afterRepairCycle` is the number of failed verification attempts that activates
111
+ it. With the default value of `2`, the first repair still uses the normal
112
+ executor; after the repaired work fails verification again, subsequent
113
+ execution uses the fallback executor.
114
+
108
115
  Restart Pi or use `/reload` after manually editing the file.
109
116
 
110
117
  Version 1 configuration used one top-level `provider`. Goala reads that schema,
@@ -218,7 +218,7 @@ export const GOALA_SETUP_PRESETS: readonly GoalaSetupPreset[] = [
218
218
  },
219
219
  {
220
220
  id: "current",
221
- label: "Use the current model for every phase",
221
+ label: "Use the current model for every role",
222
222
  create: (currentModel) =>
223
223
  currentModel
224
224
  ? currentModelConfig(currentModel.provider, currentModel.id)
@@ -250,13 +250,14 @@ export function formatConfig(config: GoalaConfig): string {
250
250
  `Executor: ${formatProfile(config.executor)}`,
251
251
  `Step verifier: ${formatProfile(config.stepVerifier)}`,
252
252
  `Verifier: ${formatProfile(config.verifier)}`,
253
- `Fallback: ${formatProfile(config.fallbackExecutor)} from repair ${config.fallbackExecutor.afterRepairCycle}`,
253
+ `Fallback executor: ${formatProfile(config.fallbackExecutor)}`,
254
+ `Fallback executor activates after: ${config.fallbackExecutor.afterRepairCycle} failed verification attempts`,
254
255
  `Review policy: ${config.reviewPolicy}`,
255
256
  `Memory: ${config.memory.enabled ? "enabled" : "disabled"}; auto-recall ${config.memory.autoRecall ? "on" : "off"}`,
256
- `Current-model fallback: ${config.allowCurrentModelFallback ? "allowed" : "disabled"}`,
257
+ `Unavailable-model fallback to current Pi model: ${config.allowCurrentModelFallback ? "allowed" : "disabled"}`,
257
258
  ].join("\n");
258
259
  }
259
260
 
260
261
  function formatProfile(profile: ModelProfile): string {
261
- return `${profile.provider}/${profile.model}:${profile.thinkingLevel}`;
262
+ return `${profile.provider}/${profile.model} · ${profile.thinkingLevel} reasoning`;
262
263
  }
@@ -10,7 +10,6 @@ import {
10
10
  loadConfig,
11
11
  writeConfig,
12
12
  type GoalaConfig,
13
- type ModelProfile,
14
13
  type ReviewPolicy,
15
14
  type ThinkingLevel,
16
15
  } from "./config.ts";
@@ -37,6 +36,7 @@ import {
37
36
  findRecoverableGoals,
38
37
  formatRecoveryStatus,
39
38
  } from "./recovery.ts";
39
+ import { modelProfileForPhase } from "./routing.ts";
40
40
  import {
41
41
  moveToFreshSession,
42
42
  slicePhaseContext,
@@ -102,23 +102,6 @@ export default function goala(pi: ExtensionAPI): void {
102
102
  displayPlan(pi, state);
103
103
  }
104
104
 
105
- function profileForPhase(): ModelProfile {
106
- if (
107
- state.phase === "planning" ||
108
- state.phase === "awaiting-execution" ||
109
- state.phase === "awaiting-review"
110
- ) return config.planner;
111
- if (state.phase === "verifying-step") return config.stepVerifier;
112
- if (state.phase === "verifying") return config.verifier;
113
- if (
114
- state.phase === "executing" &&
115
- state.repairCycles >= config.fallbackExecutor.afterRepairCycle
116
- ) {
117
- return config.fallbackExecutor;
118
- }
119
- return config.executor;
120
- }
121
-
122
105
  async function statusForSession(
123
106
  ctx: ExtensionCommandContext,
124
107
  ): Promise<string> {
@@ -143,7 +126,11 @@ export default function goala(pi: ExtensionAPI): void {
143
126
  return true;
144
127
  }
145
128
 
146
- const profile = profileForPhase();
129
+ const profile = modelProfileForPhase(
130
+ config,
131
+ state.phase,
132
+ state.repairCycles,
133
+ );
147
134
  let model = ctx.modelRegistry.find(profile.provider, profile.model);
148
135
  if (!model && config.allowCurrentModelFallback) {
149
136
  const fallback = sessionDefaultModel ?? (ctx.model
@@ -563,12 +550,12 @@ Do not edit files or rely on executor claims. Finish with submit_step_verificati
563
550
  if (!choice && ctx.hasUI) {
564
551
  const selected = await ctx.ui.select("Goala setup", [
565
552
  ...GOALA_SETUP_PRESETS.map((preset) => preset.label),
566
- "Configure each phase",
553
+ "Configure each role",
567
554
  "Show current configuration",
568
555
  ]);
569
556
  choice = selected === "Show current configuration"
570
557
  ? "status"
571
- : selected === "Configure each phase"
558
+ : selected === "Configure each role"
572
559
  ? "custom"
573
560
  : GOALA_SETUP_PRESETS.find((preset) => preset.label === selected)?.id ?? "";
574
561
  }
@@ -0,0 +1,28 @@
1
+ import type {
2
+ GoalaConfig,
3
+ ModelProfile,
4
+ } from "./config.ts";
5
+ import type { Phase } from "./workflow.ts";
6
+
7
+ export function modelProfileForPhase(
8
+ config: GoalaConfig,
9
+ phase: Phase,
10
+ repairCycles: number,
11
+ ): ModelProfile {
12
+ if (
13
+ phase === "planning" ||
14
+ phase === "awaiting-execution" ||
15
+ phase === "awaiting-review"
16
+ ) {
17
+ return config.planner;
18
+ }
19
+ if (phase === "verifying-step") return config.stepVerifier;
20
+ if (phase === "verifying") return config.verifier;
21
+ if (
22
+ phase === "executing" &&
23
+ repairCycles >= config.fallbackExecutor.afterRepairCycle
24
+ ) {
25
+ return config.fallbackExecutor;
26
+ }
27
+ return config.executor;
28
+ }
@@ -21,7 +21,7 @@ const MODEL_ROLES: ReadonlyArray<{ key: ModelRole; label: string }> = [
21
21
  { key: "executor", label: "Executor" },
22
22
  { key: "stepVerifier", label: "Step verifier" },
23
23
  { key: "verifier", label: "Final verifier" },
24
- { key: "fallbackExecutor", label: "Repair fallback" },
24
+ { key: "fallbackExecutor", label: "Fallback executor" },
25
25
  ];
26
26
 
27
27
  const THINKING_LEVELS: readonly ThinkingLevel[] = [
@@ -38,7 +38,7 @@ const KEEP_CURRENT = "Keep current";
38
38
  const CHANGE_REASONING = "Change reasoning effort";
39
39
  const CHANGE_MODEL = "Change provider or model";
40
40
  const SAVE_CONFIGURATION = "Save configuration";
41
- const EDIT_PHASE = "Edit a phase";
41
+ const EDIT_ROLE = "Edit a role";
42
42
  const CANCEL = "Cancel";
43
43
 
44
44
  function titleCase(value: string): string {
@@ -225,8 +225,12 @@ async function configureRole(
225
225
  ...(currentModel?.reasoning ? [CHANGE_REASONING] : []),
226
226
  CHANGE_MODEL,
227
227
  ];
228
+ const activation =
229
+ role.key === "fallbackExecutor"
230
+ ? `\nActivates after ${config.fallbackExecutor.afterRepairCycle} failed verification attempts`
231
+ : "";
228
232
  const action = await ctx.ui.select(
229
- `${role.label}\nCurrent: ${profileLabel(ctx, available, current)}`,
233
+ `${role.label}${activation}\nCurrent: ${profileLabel(ctx, available, current)}`,
230
234
  actions,
231
235
  );
232
236
  if (!action) return false;
@@ -251,8 +255,13 @@ function reviewSummary(
251
255
  config: GoalaConfig,
252
256
  ): string {
253
257
  return MODEL_ROLES.map(
254
- (role) =>
255
- `${role.label}: ${profileLabel(ctx, available, config[role.key])}`,
258
+ (role) => {
259
+ const label =
260
+ role.key === "fallbackExecutor"
261
+ ? `${role.label} (after ${config.fallbackExecutor.afterRepairCycle} failed verification attempts)`
262
+ : role.label;
263
+ return `${label}: ${profileLabel(ctx, available, config[role.key])}`;
264
+ },
256
265
  ).join("\n");
257
266
  }
258
267
 
@@ -261,7 +270,7 @@ export async function selectModelRoles(
261
270
  current: GoalaConfig,
262
271
  ): Promise<GoalaConfig | undefined> {
263
272
  if (!ctx.hasUI) {
264
- ctx.ui.notify("Per-phase model setup requires interactive Pi.", "warning");
273
+ ctx.ui.notify("Per-role model setup requires interactive Pi.", "warning");
265
274
  return undefined;
266
275
  }
267
276
 
@@ -287,13 +296,13 @@ export async function selectModelRoles(
287
296
  while (true) {
288
297
  const action = await ctx.ui.select(
289
298
  `Review Goala configuration\n${reviewSummary(ctx, available, proposed)}`,
290
- [SAVE_CONFIGURATION, EDIT_PHASE, CANCEL],
299
+ [SAVE_CONFIGURATION, EDIT_ROLE, CANCEL],
291
300
  );
292
301
  if (!action || action === CANCEL) return undefined;
293
302
  if (action === SAVE_CONFIGURATION) return proposed;
294
303
 
295
304
  const selectedRole = await ctx.ui.select(
296
- "Choose a phase to edit",
305
+ "Choose a role to edit",
297
306
  MODEL_ROLES.map((role) => role.label),
298
307
  );
299
308
  if (!selectedRole) return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goala",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Goal-oriented agent lifecycle, evidence, and verified memory for Pi.",
5
5
  "type": "module",
6
6
  "author": "Barry King",