pi-delegation-policy 0.3.1 → 0.4.0

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
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.0 - 2026-08-27
6
+
7
+ ### Added
8
+
9
+ - Added a compact effective-policy preview, selector guidance, and available public model metadata to the `/delegate` panel.
10
+
11
+ ### Changed
12
+
13
+ - Require exact `provider/model` references for delegation guidance instead of relying on a launcher default.
14
+ - Clarified task-fit role selection, preference tie-breaks, dynamic advisory thinking, and current panel and status diagnostics.
15
+ - Raised the Pi peer requirement and explicitly checked baseline to `0.84.3`.
16
+
17
+ ## 0.3.2 - 2026-08-26
18
+
19
+ ### Documentation
20
+
21
+ - Refreshed the README and wiki with the current package status, safe first-use path, panel behavior, configuration hierarchy, and privacy boundaries.
22
+
5
23
  ## 0.3.1 - 2026-08-26
6
24
 
7
25
  ### Fixed
package/README.md CHANGED
@@ -1,130 +1,55 @@
1
1
  # pi-delegation-policy
2
2
 
3
- A local Pi extension that lets you choose delegation intensity and exact model references for Small, Medium, Large, and an optional UI Design role. It guides the main agent; it does not run, route, or enforce delegated work.
3
+ A local Pi extension that helps the main agent decide **when delegation is worth it** and which exact models to use for the Small, Medium, Large, and optional UI Design roles. It provides guidance; it is not a subagent runner.
4
4
 
5
- > **Status:** Version 0.3.0 is available from npm.
5
+ > **Status:** Version **0.4.0** is the latest published package. The package requires Pi `>=0.84.3`; Pi `0.84.3` is the explicitly checked baseline (newer versions are not claimed as tested).
6
6
  >
7
- > **Documentation:** Read the [documentation site](https://yivas.github.io/pi-delegation-policy/).
7
+ > **Docs:** [Read the documentation site](https://yivas.github.io/pi-delegation-policy/).
8
8
 
9
- ## What it does
9
+ ## Value and boundary
10
10
 
11
- - Sets delegation intensity to `off`, `normal`, or `aggressive` globally or for the current session branch.
12
- - Keeps global defaults for intensity, model references, preference, and the optional UI Design role.
13
- - Stores session changes in Pi's session branch, so they survive reload, resume, and tree navigation.
14
- - Uses Pi's scoped models when configured, otherwise its available authenticated models.
15
- - Injects one policy block through Pi's public `before_agent_start` event when the active configuration is valid.
11
+ - Choose `off`, `normal`, or `aggressive` delegation intensity globally or for the current session branch.
12
+ - Configure exact provider/model references for Small, Medium, Large, and optionally UI Design.
13
+ - Keep global defaults and session-branch overrides across reload, resume, and tree navigation.
14
+ - Validate active configurations before injecting one policy block through Pi's public `before_agent_start` event.
16
15
 
17
- ## What it does not do
16
+ The extension guides the main agent. It never creates, launches, routes, supervises, or blocks subagents; changes Pi's main model or thinking; stores credentials; intercepts tools; or makes its own network requests. It has no model fallback, telemetry, project configuration, presets, or external skill loading.
18
17
 
19
- This package does not create, launch, route, supervise, or block subagents. It does not change Pi's main model or thinking level. It has no presets, project configuration, external skill loading, tool interception, model fallback, telemetry, credential storage, or network requests.
18
+ ## Install and start
20
19
 
21
- ## Install
22
-
23
- Install the package in your Pi user settings:
20
+ Install the published package in Pi's user settings, then reload Pi:
24
21
 
25
22
  ```bash
26
23
  pi install npm:pi-delegation-policy
24
+ # restart Pi, or run /reload
27
25
  ```
28
26
 
29
- It supports Pi `0.84.1`. Restart Pi or run `/reload` after installation. To install a local checkout instead, use `pi install ./pi-delegation-policy`.
27
+ 1. Open `/delegate` (or press `Alt+G` in Pi's TUI).
28
+ 2. Configure exact, authenticated Small, Medium, and Large provider/model references. UI Design is optional.
29
+ 3. Select `normal` or `aggressive`, then choose **Apply changes**.
30
+ 4. Run `/delegate status` and inspect the exact role references and their sources. `D:ERR` means an active required role is invalid; no policy is injected.
31
+ 5. The applied state affects the **next** agent run. An agent already running is not rewritten.
32
+
33
+ See the [end-to-end getting-started guide](https://yivas.github.io/pi-delegation-policy/getting-started/) and [configuration reference](https://yivas.github.io/pi-delegation-policy/configuration/) for details.
30
34
 
31
- ## Commands
35
+ ## Essential commands
32
36
 
33
37
  ```text
34
- /delegate Open the keyboard-first selector
35
- /delegate off Disable policy injection for this session branch
38
+ /delegate Open the editor
39
+ /delegate off Disable policy for this session branch
36
40
  /delegate normal Enable balanced delegation guidance
37
41
  /delegate aggressive Enable delegation-first guidance
38
- /delegate status Show the effective session state
39
- /delegate reset Reset this session branch to off
42
+ /delegate status Show effective session state
43
+ /delegate reset Reset this branch to off and other fields to global defaults
40
44
  ```
41
45
 
42
- `Alt+G` opens the same editor when the shortcut is available. There is no separate off shortcut; use `/delegate off` or choose `off` in the editor. Changes apply to the next agent run. An agent already running keeps the system prompt it started with.
43
-
44
- The interactive editor requires Pi's TUI mode; quick `/delegate` arguments remain available in other modes. The editor is one bounded, keyboard-first panel. It shows the effective value and the built-in, global, and session value for each setting. Model fields use the same compact presentation as Pi's `/model` selector: model ID first with `[provider]` at the end, live fuzzy search over provider, model ID, and display name, and at most ten visible model rows before scrolling. **Use global default** remains available while searching, and UI Design also offers **Disable for this session**.
45
-
46
- Edits stay in a draft until **Apply changes** is selected or `A` is pressed. **Save effective configuration as defaults** updates the global file without applying the session draft. **Reset draft to off** remains local until Apply. Escape returns from a field editor; closing a modified draft requires explicit discard confirmation.
47
-
48
- The footer shows `D:OFF`, `D:NORM`, `D:AGG`, or `D:ERR` without replacing Pi's own status.
49
-
50
- ## Configuration
51
-
52
- Global defaults live at:
53
-
54
- ```text
55
- ~/.pi/agent/delegation-policy.json
56
- ```
57
-
58
- They use schema version 2. The file stores optional intensity, model references, preference, and an optional UI Design model. It never stores thinking.
59
-
60
- ```json
61
- {
62
- "schemaVersion": 2,
63
- "intensity": "normal",
64
- "preference": "standard",
65
- "small": {
66
- "provider": "example-provider",
67
- "model": "example-small"
68
- },
69
- "medium": {
70
- "provider": "example-provider",
71
- "model": "example-medium"
72
- },
73
- "large": {
74
- "provider": "example-provider",
75
- "model": "example-large"
76
- },
77
- "uiDesign": {
78
- "provider": "example-provider",
79
- "model": "example-ui-design"
80
- }
81
- }
82
- ```
83
-
84
- See [`examples/global.json`](examples/global.json) and the bundled [JSON Schema](schema/delegation-policy.schema.json). The values are fictional.
85
-
86
- Schema version 1 is inactive and is not migrated automatically. Open `/delegate`, configure schema version 2, and save the effective configuration as defaults before using an active intensity.
87
-
88
- ### Global defaults and session branches
46
+ The editor is a bounded, keyboard-first panel. It shows model ID first and `[provider]` last, fuzzy-searches provider, model ID, and display name, and shows at most 10 model rows. It also shows a compact effective-policy preview, field explanations, and public model metadata when Pi supplies it. Changes are drafts until **Apply changes**; saving effective configuration as defaults updates the global file without applying the draft, and closing a modified draft requires explicit discard.
89
47
 
90
- A session branch inherits global intensity, model references, preference, and UI Design until it records matching overrides. If global intensity is absent, the built-in default is `off`. A fork restores the latest valid delegation entry in its active history. The intensity selector can return a branch to **Use global default**.
48
+ ## Configuration and safety
91
49
 
92
- Selecting **Save effective configuration as defaults** copies the complete effective configuration, including intensity, to the global file. `/delegate reset` remains an explicit safety action: it writes a branch state with `off` and returns every other field to its global default.
50
+ Global defaults are stored at `~/.pi/agent/delegation-policy.json` (schema version 2). The file stores optional intensity, preference, exact role references, and an optional UI Design reference; it never stores thinking. A valid active mode requires exact, available, in-scope, authenticated Small, Medium, and Large references. Before every delegated launch, the policy requires the selected role's exact combined reference as `model: "provider/model"`; it does not rely on an ambient launcher default. Invalid active configuration fails closed as `D:ERR` with no policy injection; `off` is always `D:OFF`.
93
51
 
94
- ### Intensity
95
-
96
- - `off` injects nothing into the next agent run. Pi rebuilds the system prompt for each run, so a policy injected into an earlier run is absent; an agent already running is not rewritten. Invalid or incomplete defaults still show `D:OFF`.
97
- - `normal` delegates substantial, separable work only when the expected benefit clearly outweighs briefing, supervision, review, and integration. It keeps borderline work with the main agent.
98
- - `aggressive` delegates substantial, separable, independently checkable work by default when it has a clear objective and acceptance criteria. A plausible benefit can be enough, but tightly coupled work or clearly prohibitive overhead stays with the main agent.
99
-
100
- The main agent keeps global strategy, coordination, integration, final review, and work whose essential context is too costly or risky to transfer in every mode.
101
-
102
- ### Model roles and preference
103
-
104
- Active modes require exact `provider` and `model` references for Small, Medium, and Large. Pi must expose each reference in the current scope or available model catalog, and its provider must be authenticated. A missing, out-of-scope, unavailable, or unauthenticated role produces `D:ERR` and injects no policy. The extension never substitutes another model or role.
105
-
106
- The policy chooses a role and thinking together from task demand, difficulty, and quantity. No single factor decides the role:
107
-
108
- - Small is habitual for bounded, planned, and verifiable execution. Difficult but well-defined work can remain Small with higher thinking.
109
- - Medium can be selected directly when the combined demands materially require planning, ambiguity reduction, broad synthesis, several-module tracing, comparison, context coordination, or difficult decisions. Small does not need to fail first.
110
- - Large is exceptional and only unblocks genuinely stuck work, such as persistent failures, severe framework conflicts, or contradictory hypotheses. Reliable prior evidence can justify it without ceremonial failed attempts.
111
- - Large quantities of repetitive, independent work favor multiple Small delegations. Agent type does not determine the model role.
112
-
113
- Preference shifts credible Small/Medium choices; a clearly better task fit overrides it:
114
-
115
- - `efficient` favors Small more strongly and uses Medium when it provides a material advantage.
116
- - `standard` reproduces the canonical policy and chooses Small on a genuine Small/Medium tie.
117
- - `intensive` normally favors Medium for non-trivial bounded work when both roles are credible, while retaining Small for clearly narrow, routine, mechanical, or especially clear Small work.
118
-
119
- All three ordinary roles remain available in every preference. The main agent chooses thinking for each delegated task from task demand, difficulty, quantity, and model capabilities. Thinking is not configured or persisted by this extension.
120
-
121
- UI Design is optional. When configured, it is limited to visual design direction, exploration, and review. It must not implement an interface, write code, or run tests. When it is off, ordinary roles handle design-related work.
122
-
123
- ## Security and privacy
124
-
125
- The extension stores intensity, preference, and provider/model identifiers in local configuration and session entries. It does not store credentials, send telemetry, or make network requests. Review configuration before using it and remove credentials, prompts, personal paths, session files, and unredacted logs from reports.
126
-
127
- Read [`SECURITY.md`](SECURITY.md) for reporting guidance.
52
+ The extension stores only policy settings and provider/model identifiers in local defaults and session entries. Review local configuration before sharing diagnostics, and remove credentials, prompts, personal paths, session files, and unredacted logs from reports. See the [limits and privacy reference](https://yivas.github.io/pi-delegation-policy/limits-and-privacy/) and [security policy](https://github.com/Yivas/pi-delegation-policy/blob/main/SECURITY.md).
128
53
 
129
54
  ## Development
130
55
 
@@ -138,12 +63,8 @@ npm run build
138
63
  npm run pack:check
139
64
  ```
140
65
 
141
- The tests use local mocks and make no paid model calls or network requests.
142
-
143
- ## Contributing
144
-
145
- Read [`CONTRIBUTING.md`](CONTRIBUTING.md). Contributions must preserve the boundary: this extension guides the main agent and does not become a subagent runner, tool interceptor, credential store, or telemetry client.
66
+ Tests use local mocks and do not make paid model calls or network requests. See [CONTRIBUTING.md](https://github.com/Yivas/pi-delegation-policy/blob/main/CONTRIBUTING.md) for contribution guidance.
146
67
 
147
68
  ## License
148
69
 
149
- MIT. See [`LICENSE`](LICENSE).
70
+ MIT. See [LICENSE](https://github.com/Yivas/pi-delegation-policy/blob/main/LICENSE).
package/SECURITY.md CHANGED
@@ -14,4 +14,4 @@ Include the affected version or commit, operating system, Pi version, reproducti
14
14
 
15
15
  ## Supported versions
16
16
 
17
- Only the latest published version is supported. Version 0.3.0 is the current supported release.
17
+ Only the latest published version is supported. Version 0.4.0 is the current supported release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-delegation-policy",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "A Pi extension for configurable delegation intensity and exact subagent role model references.",
6
6
  "type": "module",
@@ -36,12 +36,14 @@
36
36
  "pack:check": "npm pack --dry-run"
37
37
  },
38
38
  "peerDependencies": {
39
- "@earendil-works/pi-coding-agent": ">=0.84.1"
39
+ "@earendil-works/pi-ai": ">=0.84.3",
40
+ "@earendil-works/pi-coding-agent": ">=0.84.3",
41
+ "@earendil-works/pi-tui": ">=0.84.3"
40
42
  },
41
43
  "devDependencies": {
42
- "@earendil-works/pi-ai": "0.84.1",
43
- "@earendil-works/pi-coding-agent": "0.84.1",
44
- "@earendil-works/pi-tui": "0.84.1",
44
+ "@earendil-works/pi-ai": "0.84.3",
45
+ "@earendil-works/pi-coding-agent": "0.84.3",
46
+ "@earendil-works/pi-tui": "0.84.3",
45
47
  "@eslint/js": "^9.39.5",
46
48
  "@types/node": "^22.0.0",
47
49
  "ajv": "^8.20.0",
@@ -12,6 +12,7 @@ import {
12
12
  type TUI,
13
13
  } from "@earendil-works/pi-tui";
14
14
  import { resolveDelegateState } from "./config.ts";
15
+ import { buildPolicyPreview } from "./prompt.ts";
15
16
  import {
16
17
  INTENSITIES,
17
18
  PREFERENCES,
@@ -52,6 +53,7 @@ export interface DelegatePanelOptions {
52
53
  session: SessionDelegateState;
53
54
  candidates: Model<Api>[];
54
55
  diagnostics: string[];
56
+ hasRuntimeError: boolean;
55
57
  onApply: (draft: SessionDelegateState) => Promise<boolean>;
56
58
  onSaveDefaults: (draft: SessionDelegateState) => Promise<GlobalDefaults | undefined>;
57
59
  onDone: (result: DelegatePanelResult) => void;
@@ -74,6 +76,15 @@ const FIELD_LABELS: Record<DelegateField, string> = {
74
76
  uiDesign: "UI Design",
75
77
  };
76
78
 
79
+ const FIELD_DESCRIPTIONS: Record<DelegateField, string> = {
80
+ intensity: "When delegation is worth considering.",
81
+ preference: "Tie-break only; task fit decides the role first.",
82
+ small: "Bounded, planned, and verifiable execution.",
83
+ medium: "Planning, ambiguity, synthesis, and coordination.",
84
+ large: "Exceptional unblocker for persistent hard problems.",
85
+ uiDesign: "Visual direction and review only; never implementation.",
86
+ };
87
+
77
88
  const ACTION_LABELS: Record<PanelAction, string> = {
78
89
  apply: "Apply changes",
79
90
  "save-defaults": "Save effective configuration as defaults",
@@ -132,6 +143,17 @@ function sortedModels(models: readonly Model<Api>[]): Model<Api>[] {
132
143
  });
133
144
  }
134
145
 
146
+ function modelMetadata(model: Model<Api> | undefined): string[] {
147
+ if (!model) return [];
148
+ const details: string[] = [];
149
+ if (model.name !== undefined) details.push(`Model Name: ${model.name}`);
150
+ if (model.api !== undefined) details.push(`API: ${model.api}`);
151
+ if (model.reasoning !== undefined) details.push(`Reasoning: ${model.reasoning ? "yes" : "no"}`);
152
+ if (model.contextWindow !== undefined) details.push(`Context: ${model.contextWindow}`);
153
+ if (model.maxTokens !== undefined) details.push(`Max output: ${model.maxTokens}`);
154
+ return details;
155
+ }
156
+
135
157
  function visibleBlockRange(blocks: string[][], selected: number, budget: number): [number, number] {
136
158
  if (blocks.length === 0) return [0, 0];
137
159
  const safeSelected = Math.max(0, Math.min(selected, blocks.length - 1));
@@ -169,6 +191,7 @@ export class DelegatePanel implements Component, Focusable {
169
191
  private readonly theme: Theme;
170
192
  private readonly candidates: Model<Api>[];
171
193
  private readonly diagnostics: string[];
194
+ private readonly hasRuntimeError: boolean;
172
195
  private readonly onApply: DelegatePanelOptions["onApply"];
173
196
  private readonly onSaveDefaults: DelegatePanelOptions["onSaveDefaults"];
174
197
  private readonly onDone: DelegatePanelOptions["onDone"];
@@ -191,6 +214,7 @@ export class DelegatePanel implements Component, Focusable {
191
214
  this.draft = cloneSession(options.session);
192
215
  this.candidates = sortedModels(options.candidates);
193
216
  this.diagnostics = [...options.diagnostics];
217
+ this.hasRuntimeError = options.hasRuntimeError;
194
218
  this.onApply = options.onApply;
195
219
  this.onSaveDefaults = options.onSaveDefaults;
196
220
  this.onDone = options.onDone;
@@ -332,9 +356,7 @@ export class DelegatePanel implements Component, Focusable {
332
356
  ];
333
357
  const notice =
334
358
  this.message ??
335
- (this.diagnostics.length > 0
336
- ? { kind: "error" as const, text: "Warning: global defaults are invalid." }
337
- : undefined);
359
+ (this.diagnostics[0] ? { kind: "error" as const, text: this.diagnostics[0] } : undefined);
338
360
  if (notice) {
339
361
  lines.push(
340
362
  truncateToWidth(
@@ -365,6 +387,8 @@ export class DelegatePanel implements Component, Focusable {
365
387
 
366
388
  private renderSettings(width: number, budget: number): string[] {
367
389
  const effective = resolveDelegateState(this.global, this.draft);
390
+ const preview = this.renderPolicyPreview(width, budget, effective);
391
+ const settingsBudget = Math.max(1, budget - preview.length);
368
392
  const blocks = SETTINGS_ITEMS.map((item, index) => {
369
393
  const selected = index === this.settingsIndex;
370
394
  if (FIELD_IDS.includes(item as DelegateField)) {
@@ -381,6 +405,7 @@ export class DelegatePanel implements Component, Focusable {
381
405
  if (width < 48) {
382
406
  return [
383
407
  selectedLine(this.theme, FIELD_LABELS[field], width, selected),
408
+ ...wrapTextWithAnsi(` ${FIELD_DESCRIPTIONS[field]}`, width),
384
409
  ...wrapTextWithAnsi(` ${value}`, width),
385
410
  ...details.flatMap((line) =>
386
411
  wrapTextWithAnsi(this.theme.fg("dim", ` ${line}`), width),
@@ -391,7 +416,10 @@ export class DelegatePanel implements Component, Focusable {
391
416
  const first = `${FIELD_LABELS[field].padEnd(labelWidth)}${value}`;
392
417
  return [
393
418
  selectedLine(this.theme, first, width, selected),
394
- ...wrapTextWithAnsi(this.theme.fg("dim", ` ${details.join(" · ")}`), width),
419
+ ...wrapTextWithAnsi(
420
+ this.theme.fg("dim", ` ${FIELD_DESCRIPTIONS[field]} · ${details.join(" · ")}`),
421
+ width,
422
+ ),
395
423
  ];
396
424
  }
397
425
  const action = item as PanelAction;
@@ -401,7 +429,25 @@ export class DelegatePanel implements Component, Focusable {
401
429
  return [disabled ? this.theme.fg("dim", line) : line];
402
430
  });
403
431
 
404
- return this.renderBlockViewport(blocks, this.settingsIndex, width, budget);
432
+ return [
433
+ ...preview,
434
+ ...this.renderBlockViewport(blocks, this.settingsIndex, width, settingsBudget),
435
+ ];
436
+ }
437
+
438
+ private renderPolicyPreview(
439
+ width: number,
440
+ budget: number,
441
+ effective: ReturnType<typeof resolveDelegateState>,
442
+ ): string[] {
443
+ const lines =
444
+ effective.intensity !== "off" && this.hasRuntimeError
445
+ ? ["D:ERR · policy unavailable; fix the reported role diagnostics"]
446
+ : buildPolicyPreview(effective);
447
+ const maximum = width >= 60 && budget >= 8 ? 4 : 2;
448
+ return ["Effective policy preview", ...lines]
449
+ .slice(0, maximum)
450
+ .map((line) => truncateToWidth(this.theme.fg("dim", line), width, ""));
405
451
  }
406
452
 
407
453
  private sourceDetails(field: DelegateField): string[] {
@@ -439,12 +485,30 @@ export class DelegatePanel implements Component, Focusable {
439
485
  mode: Extract<PanelMode, { kind: "enum" }>,
440
486
  ): string[] {
441
487
  const values = mode.field === "intensity" ? INTENSITIES : PREFERENCES;
488
+ const descriptions =
489
+ mode.field === "intensity"
490
+ ? {
491
+ off: "No policy is injected.",
492
+ normal: "Delegate when the expected benefit clearly outweighs overhead.",
493
+ aggressive: "Delegate suitable substantial work by default.",
494
+ }
495
+ : {
496
+ efficient: "Tie-break comparable fits toward Small.",
497
+ standard: "No extra Small or Medium bias.",
498
+ intensive: "Tie-break comparable fits toward Medium.",
499
+ };
500
+ const globalValue =
501
+ this.global[mode.field] ?? (mode.field === "intensity" ? "off" : "standard");
442
502
  const options = [
443
- `${USE_GLOBAL_DEFAULT} (${this.global[mode.field] ?? (mode.field === "intensity" ? "off" : "standard")})`,
444
- ...values,
503
+ {
504
+ label: `${USE_GLOBAL_DEFAULT} (${globalValue})`,
505
+ description: "Use the current global value.",
506
+ },
507
+ ...values.map((value) => ({ label: value, description: descriptions[value] })),
445
508
  ];
446
509
  const blocks = options.map((option, index) => [
447
- selectedLine(this.theme, option, width, index === mode.selected),
510
+ selectedLine(this.theme, option.label, width, index === mode.selected),
511
+ ...wrapTextWithAnsi(this.theme.fg("dim", ` ${option.description}`), width),
448
512
  ]);
449
513
  return this.renderBlockViewport(blocks, mode.selected, width, budget);
450
514
  }
@@ -471,15 +535,20 @@ export class DelegatePanel implements Component, Focusable {
471
535
  );
472
536
  const dividerRows = budget > pinnedLines.length + 3 ? 1 : 0;
473
537
  const selectedChoice = choices[mode.selected];
474
- const selectedName =
475
- selectedChoice?.kind === "model" && selectedChoice.description
476
- ? [` Model Name: ${selectedChoice.description}`]
538
+ const metadata =
539
+ selectedChoice?.kind === "model"
540
+ ? modelMetadata(
541
+ this.candidates.find(
542
+ (model) =>
543
+ modelKey({ provider: model.provider, model: model.id }) === selectedChoice.key,
544
+ ),
545
+ )
477
546
  : [];
478
- const detailRows = selectedName.length > 0 && budget > pinnedLines.length + 6 ? 2 : 0;
479
- const availableListRows = Math.max(
480
- 0,
481
- budget - 2 - pinnedLines.length - dividerRows - detailRows,
482
- );
547
+ const fixedRows = 2 + pinnedLines.length + dividerRows;
548
+ const availableDetailRows = Math.max(0, budget - fixedRows - 1);
549
+ const details = metadata.slice(0, availableDetailRows);
550
+ const detailRows = details.length > 0 ? details.length + 1 : 0;
551
+ const availableListRows = Math.max(0, budget - fixedRows - detailRows);
483
552
  const listBudget = Math.min(11, availableListRows);
484
553
  const modelBlocks = models.map((choice, index) => {
485
554
  const combinedIndex = index + pinnedCount;
@@ -512,7 +581,7 @@ export class DelegatePanel implements Component, Focusable {
512
581
  ...pinnedLines,
513
582
  ...(dividerRows ? [this.theme.fg("borderMuted", "─".repeat(width))] : []),
514
583
  ...modelLines,
515
- ...(detailRows ? ["", ...selectedName.map((line) => this.theme.fg("muted", line))] : []),
584
+ ...(detailRows ? ["", ...details.map((line) => this.theme.fg("muted", ` ${line}`))] : []),
516
585
  ].slice(0, budget);
517
586
  }
518
587
 
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ import type {
6
6
  import type { AutocompleteItem } from "@earendil-works/pi-tui";
7
7
  import { buildDelegationPolicy } from "./prompt.ts";
8
8
  import {
9
+ formatModelRef,
9
10
  hasRuntimeError,
10
11
  loadRuntime,
11
12
  sessionEntry,
@@ -54,19 +55,15 @@ async function openEditor(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void
54
55
  updateStatus(ctx, await refresh(ctx));
55
56
  }
56
57
 
57
- function configuredLabel(value: unknown): string {
58
- return value ? "configured" : "unset";
59
- }
60
-
61
58
  export function statusText(state: RuntimeState): string {
62
59
  const { effective } = state;
63
60
  const details = [
64
61
  `${statusLabel(state)} intensity=${effective.intensity} (${effective.source.intensity})`,
65
62
  `preference=${effective.preference} (${effective.source.preference})`,
66
- `small=${configuredLabel(effective.small)} (${effective.source.small})`,
67
- `medium=${configuredLabel(effective.medium)} (${effective.source.medium})`,
68
- `large=${configuredLabel(effective.large)} (${effective.source.large})`,
69
- `ui-design=${configuredLabel(effective.uiDesign)} (${effective.source.uiDesign})`,
63
+ `small=${formatModelRef(effective.small)} (${effective.source.small})`,
64
+ `medium=${formatModelRef(effective.medium)} (${effective.source.medium})`,
65
+ `large=${formatModelRef(effective.large)} (${effective.source.large})`,
66
+ `ui-design=${formatModelRef(effective.uiDesign)} (${effective.source.uiDesign})`,
70
67
  ];
71
68
 
72
69
  if (effective.intensity !== "off" && state.runtimeErrors.length > 0) {
package/src/prompt.ts CHANGED
@@ -1,35 +1,42 @@
1
1
  import { hasRuntimeError, type RuntimeState } from "./runtime.ts";
2
- import type { ModelRef, Preference } from "./types.ts";
2
+ import type { EffectiveDelegateState, ModelRef, Preference } from "./types.ts";
3
3
 
4
4
  const NORMAL_POLICY =
5
5
  "Delegate substantial, separable work only when the expected benefit clearly outweighs briefing, supervision, review, and integration cost. Count parallelism as a benefit only when valuable work can advance now or elapsed time matters. A merely possible fresh perspective is not enough by itself. Keep borderline work with the main agent.";
6
6
  const AGGRESSIVE_POLICY =
7
7
  "Default to delegating substantial, separable, independently checkable work with a clear objective and acceptance criteria. Delegate when the benefit is plausible even if not proven, including a useful independent perspective. Keep work with the main agent when it is poorly bounded, tightly coupled, dominated by integration or final accountability, or has clearly prohibitive delegation overhead.";
8
8
 
9
- const ROLE_SELECTION_POLICY = `Choose the role and thinking together from the combination of:
10
- - task demand: execute, search, plan, decide, or unblock;
11
- - difficulty: clarity, ambiguity, dependencies, risk, and competing hypotheses;
12
- - quantity: files, modules, systems, sources, and context volume.
13
- No single factor decides the role.
9
+ const ROLE_SELECTION_POLICY = `Choose the role by task fit before considering model preference:
10
+ - demand: execute, search, plan, decide, coordinate, or unblock;
11
+ - difficulty: clarity, ambiguity, dependencies, competing hypotheses, and risk;
12
+ - quantity: files, modules, systems, sources, and context volume;
13
+ - error and review cost: what can go wrong, how costly it is to detect, and what evidence is needed.
14
+ No single factor decides the role. Select the smallest role that can satisfy the acceptance criteria and evidence requirements.
14
15
 
15
- Use Small habitually for bounded, planned, and verifiable work: concrete searches, scoped exploration, defined implementation, focused documentation, tests, reviews, mechanical changes, evident bugs, and bounded UI implementation whose design and stack are decided. Difficult but well-defined execution can remain Small with higher thinking.
16
+ Use Small for bounded, planned, and verifiable execution: concrete searches, scoped exploration, defined implementation, focused documentation, tests, reviews, mechanical changes, evident bugs, and bounded UI implementation whose design and stack are decided. Difficult but well-defined execution can remain Small with higher thinking.
16
17
 
17
- Use Medium directly when the combined demands materially require defining a plan, reducing meaningful ambiguity, broad synthesis, tracing several modules, comparing sources or options, coordinating substantial context, or making difficult decisions. These are evidence, not automatic triggers. Small does not need to fail first.
18
+ Use Medium directly when the combined task fit materially requires planning, reducing meaningful ambiguity, broad synthesis, tracing several modules, comparing sources or options, coordinating substantial context, or making difficult decisions. Small does not need to fail first.
18
19
 
19
20
  Use Large only to unblock genuinely stuck work: persistent failures, severe framework conflicts, contradictory hypotheses, or reliable prior evidence that ordinary roles have not produced a trustworthy answer. Do not require ceremonial failed attempts. Large remains exceptional.
20
21
 
21
- Large quantities of repetitive, independent work favor multiple Small delegations; volume alone does not justify Medium or Large. Agent type does not determine the model role. A clearly better task fit overrides preference; preference only shifts credible Small/Medium choices.
22
+ Large quantities of repetitive, independent work favor multiple Small delegations; volume alone does not justify Medium or Large. Agent type does not determine the model role. Apply preference only when Small and Medium are comparably credible fits.
22
23
 
23
24
  In every intensity, keep global strategy, coordination, integration, final review, and work whose essential context is too costly or risky to transfer with the main agent.`;
24
25
 
25
26
  function preferenceGuidance(preference: Preference): string {
26
27
  if (preference === "efficient") {
27
- return "Favor Small more strongly than standard. When Small can safely satisfy the acceptance criteria, choose it unless Medium provides a material advantage.";
28
+ return "Use efficient only as a Small tie-break when Small and Medium are comparably credible. Do not choose Small when Medium is a materially better task fit.";
28
29
  }
29
30
  if (preference === "intensive") {
30
- return "When Small and Medium are both credible, normally prefer Medium. Keep Small for work that is clearly narrow, routine, mechanical, or an especially clear Small fit.";
31
+ return "Use intensive only as a Medium tie-break when Small and Medium are comparably credible. Do not choose Medium when Small is the clearly better task fit.";
31
32
  }
32
- return "Choose Small on a genuine Small/Medium tie; otherwise follow the role-selection policy above.";
33
+ return "Standard adds no Small or Medium bias; follow task fit.";
34
+ }
35
+
36
+ function preferencePreview(preference: Preference): string {
37
+ if (preference === "efficient") return "efficient breaks comparable fits toward Small";
38
+ if (preference === "intensive") return "intensive breaks comparable fits toward Medium";
39
+ return "standard has no extra bias";
33
40
  }
34
41
 
35
42
  function promptString(value: string): string {
@@ -43,6 +50,25 @@ function formatReference(reference: ModelRef): string {
43
50
  return `provider=${promptString(reference.provider)} model=${promptString(reference.model)}`;
44
51
  }
45
52
 
53
+ function formatLaunchModel(reference: ModelRef): string {
54
+ return promptString(`${reference.provider}/${reference.model}`);
55
+ }
56
+
57
+ export function buildPolicyPreview(effective: EffectiveDelegateState): string[] {
58
+ if (effective.intensity === "off") return ["off · no policy injected"];
59
+
60
+ const { small, medium, large } = effective;
61
+ if (!small) return ["active · Small not configured · no policy can be injected"];
62
+ if (!medium) return ["active · Medium not configured · no policy can be injected"];
63
+ if (!large) return ["active · Large not configured · no policy can be injected"];
64
+
65
+ return [
66
+ `${effective.intensity} · task fit first · ${preferencePreview(effective.preference)}`,
67
+ `Small ${formatLaunchModel(small)} · Medium ${formatLaunchModel(medium)} · Large ${formatLaunchModel(large)}`,
68
+ "Every launch must include the selected exact model; thinking stays dynamic.",
69
+ ];
70
+ }
71
+
46
72
  export function buildDelegationPolicy(state: RuntimeState): string | undefined {
47
73
  if (state.effective.intensity === "off" || hasRuntimeError(state)) return undefined;
48
74
 
@@ -51,7 +77,7 @@ export function buildDelegationPolicy(state: RuntimeState): string | undefined {
51
77
 
52
78
  const intensityPolicy = effective.intensity === "normal" ? NORMAL_POLICY : AGGRESSIVE_POLICY;
53
79
  const uiDesign = effective.uiDesign
54
- ? `\n- UI Design: ${formatReference(effective.uiDesign)}. Use this role only for visual design direction, exploration, or review. Never use it to implement an interface, write code, or run tests.`
80
+ ? `\n- UI Design: ${formatReference(effective.uiDesign)}; launch with model: ${formatLaunchModel(effective.uiDesign)}. Use this role only for visual design direction, exploration, or review. Never use it to implement an interface, write code, or run tests.`
55
81
  : "";
56
82
 
57
83
  return `<delegation_policy>
@@ -62,12 +88,12 @@ ${ROLE_SELECTION_POLICY}
62
88
 
63
89
  Model preference: ${effective.preference}. ${preferenceGuidance(effective.preference)}
64
90
 
65
- Use the exact provider and model for the selected role. The references below use JSON string syntax; interpret escaped characters as JSON before use. Do not invent a fallback model or role. Choose thinking dynamically for each delegation from task demand, difficulty, quantity, and the selected model's capabilities. Do not treat thinking as persisted configuration.
91
+ Before every delegated launch, name the selected role, take its exact combined provider/model reference below, and include it in the call as model: "provider/model" using the JSON-escaped value shown for that role. Do not omit model, inherit an ambient launcher default, substitute another model, or invent a fallback model or role. Choose thinking dynamically for each delegation from task demand, difficulty, quantity, risk, review cost, and the selected model's capabilities. Thinking is advisory and is not persisted configuration.
66
92
 
67
93
  Roles:
68
- - Small: ${formatReference(effective.small)}
69
- - Medium: ${formatReference(effective.medium)}
70
- - Large: ${formatReference(effective.large)}${uiDesign}
94
+ - Small: ${formatReference(effective.small)}; launch with model: ${formatLaunchModel(effective.small)}
95
+ - Medium: ${formatReference(effective.medium)}; launch with model: ${formatLaunchModel(effective.medium)}
96
+ - Large: ${formatReference(effective.large)}; launch with model: ${formatLaunchModel(effective.large)}${uiDesign}
71
97
 
72
98
  This is guidance for the main agent. It does not create, execute, route, supervise, or enforce delegated work.
73
99
  </delegation_policy>`;
package/src/ui.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  writeConfig,
7
7
  } from "./config.ts";
8
8
  import { DelegatePanel, type DelegatePanelResult } from "./delegate-panel.ts";
9
- import { loadRuntime, modelCandidates, sessionEntry } from "./runtime.ts";
9
+ import { hasRuntimeError, loadRuntime, modelCandidates, sessionEntry } from "./runtime.ts";
10
10
 
11
11
  export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI): Promise<void> {
12
12
  if (!ctx.hasUI) return;
@@ -29,7 +29,8 @@ export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI
29
29
  global: state.global,
30
30
  session: state.session,
31
31
  candidates,
32
- diagnostics: state.diagnostics.map((diagnostic) => diagnostic.message),
32
+ diagnostics: state.runtimeErrors,
33
+ hasRuntimeError: hasRuntimeError(state),
33
34
  onApply: async (draft) => {
34
35
  const session = structuredClone(draft);
35
36
  sessionEntry(pi, { ...state, session });