pi-delegation-policy 0.1.1 → 0.2.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,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.0 - 2026-08-26
6
+
7
+ ### Added
8
+
9
+ - Added optional global intensity with session-branch overrides and a built-in `off` fallback.
10
+ - Added **Use global default** to the intensity selector.
11
+
12
+ ### Fixed
13
+
14
+ - Replaced the terminal-ambiguous `Ctrl+Shift+D` shortcut with `Ctrl+Alt+D`.
15
+
16
+ ## 0.1.2 - 2026-08-26
17
+
18
+ ### Fixed
19
+
20
+ - Restored the canonical role-selection guidance for bounded execution, planning and ambiguity, repetitive volume, and exceptional blockers.
21
+ - Made the `normal` and `aggressive` thresholds and the three Small/Medium preference biases operational at their boundaries.
22
+ - Corrected session-branch guidance: a session without policy state starts at `off`, while a fork inherits the latest valid entry in its active history.
23
+
5
24
  ## 0.1.1 - 2026-08-26
6
25
 
7
26
  ### Added
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
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.
4
4
 
5
- > **Status:** Version 0.1.1 is available from npm. Every new session starts at `off`.
5
+ > **Status:** Version 0.2.0 is available from npm.
6
6
  >
7
7
  > **Documentation:** Read the [documentation site](https://yivas.github.io/pi-delegation-policy/).
8
8
 
9
9
  ## What it does
10
10
 
11
- - Sets delegation intensity to `off`, `normal`, or `aggressive` for the current session branch.
12
- - Keeps global defaults for model references, preference, and the optional UI Design role.
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
13
  - Stores session changes in Pi's session branch, so they survive reload, resume, and tree navigation.
14
14
  - Uses Pi's scoped models when configured, otherwise its available authenticated models.
15
15
  - Injects one policy block through Pi's public `before_agent_start` event when the active configuration is valid.
@@ -39,7 +39,9 @@ It supports Pi `0.84.1`. Restart Pi or run `/reload` after installation. To inst
39
39
  /delegate reset Reset this session branch to off
40
40
  ```
41
41
 
42
- `Ctrl+Shift+D` opens the selector when the shortcut is available. The footer shows `D:OFF`, `D:NORM`, `D:AGG`, or `D:ERR` without replacing Pi's own status.
42
+ `Ctrl+Alt+D` opens the selector when the shortcut is available. There is no separate off shortcut; use `/delegate off` or choose `off` in the selector. Changes apply to the next agent run. An agent already running keeps the system prompt it started with.
43
+
44
+ The footer shows `D:OFF`, `D:NORM`, `D:AGG`, or `D:ERR` without replacing Pi's own status.
43
45
 
44
46
  ## Configuration
45
47
 
@@ -49,11 +51,12 @@ Global defaults live at:
49
51
  ~/.pi/agent/delegation-policy.json
50
52
  ```
51
53
 
52
- They use schema version 2. The file stores model references, preference, and an optional UI Design model. It never stores intensity or thinking.
54
+ They use schema version 2. The file stores optional intensity, model references, preference, and an optional UI Design model. It never stores thinking.
53
55
 
54
56
  ```json
55
57
  {
56
58
  "schemaVersion": 2,
59
+ "intensity": "normal",
57
60
  "preference": "standard",
58
61
  "small": {
59
62
  "provider": "example-provider",
@@ -80,35 +83,42 @@ Schema version 1 is inactive and is not migrated automatically. Open `/delegate`
80
83
 
81
84
  ### Global defaults and session branches
82
85
 
83
- A new session or branch always starts at `off`. It inherits global model references and preference until `/delegate` changes a field in that branch. Session fields override only their matching global values. Selecting **Save effective configuration as defaults** copies the current roles, preference, and UI Design setting to the global file without copying intensity.
86
+ 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**.
84
87
 
85
- `/delegate reset` writes a session state with `off` and returns non-intensity fields to their global defaults.
88
+ 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.
86
89
 
87
90
  ### Intensity
88
91
 
89
- - `off` injects nothing. Invalid or incomplete defaults still show `D:OFF`.
90
- - `normal` delegates substantial, bounded, independent work when doing so saves effort without losing essential context.
91
- - `aggressive` favors delegating that work when its objective and acceptance criteria are clear.
92
+ - `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`.
93
+ - `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.
94
+ - `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.
92
95
 
93
- The main agent keeps global decisions, coordination, integration, and final review in every mode.
96
+ 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.
94
97
 
95
98
  ### Model roles and preference
96
99
 
97
100
  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.
98
101
 
99
- The preference only changes the policy's selection bias:
102
+ The policy chooses a role and thinking together from task demand, difficulty, and quantity. No single factor decides the role:
103
+
104
+ - Small is habitual for bounded, planned, and verifiable execution. Difficult but well-defined work can remain Small with higher thinking.
105
+ - 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.
106
+ - 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.
107
+ - Large quantities of repetitive, independent work favor multiple Small delegations. Agent type does not determine the model role.
108
+
109
+ Preference shifts credible Small/Medium choices; a clearly better task fit overrides it:
100
110
 
101
- - `efficient` favors Small.
102
- - `standard` uses Small for routine work, Medium for planning, ambiguity, or broad synthesis, and Large for exceptional blockers.
103
- - `intensive` favors Medium.
111
+ - `efficient` favors Small more strongly and uses Medium when it provides a material advantage.
112
+ - `standard` reproduces the canonical policy and chooses Small on a genuine Small/Medium tie.
113
+ - `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.
104
114
 
105
- All three ordinary roles remain available in every preference. The main agent chooses thinking for each delegated task from the task, difficulty, volume, and model capabilities. Thinking is not configured or persisted by this extension.
115
+ 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.
106
116
 
107
117
  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.
108
118
 
109
119
  ## Security and privacy
110
120
 
111
- The extension stores provider and 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.
121
+ 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.
112
122
 
113
123
  Read [`SECURITY.md`](SECURITY.md) for reporting guidance.
114
124
 
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.1.1 is the current supported release.
17
+ Only the latest published version is supported. Version 0.2.0 is the current supported release.
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
+ "intensity": "normal",
3
4
  "preference": "standard",
4
5
  "small": {
5
6
  "provider": "example-provider",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-delegation-policy",
3
- "version": "0.1.1",
3
+ "version": "0.2.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",
@@ -6,6 +6,7 @@
6
6
  "required": ["schemaVersion"],
7
7
  "properties": {
8
8
  "schemaVersion": { "const": 2 },
9
+ "intensity": { "enum": ["off", "normal", "aggressive"] },
9
10
  "preference": { "enum": ["efficient", "standard", "intensive"] },
10
11
  "small": { "$ref": "#/$defs/modelRef" },
11
12
  "medium": { "$ref": "#/$defs/modelRef" },
package/src/config.ts CHANGED
@@ -67,8 +67,17 @@ function copyModelRef(value: ModelRef | undefined): ModelRef | undefined {
67
67
  export function parseConfig(value: unknown): GlobalDefaults | undefined {
68
68
  if (
69
69
  !isRecord(value) ||
70
- !hasOnlyKeys(value, ["schemaVersion", "preference", "small", "medium", "large", "uiDesign"]) ||
70
+ !hasOnlyKeys(value, [
71
+ "schemaVersion",
72
+ "intensity",
73
+ "preference",
74
+ "small",
75
+ "medium",
76
+ "large",
77
+ "uiDesign",
78
+ ]) ||
71
79
  value.schemaVersion !== 2 ||
80
+ (value.intensity !== undefined && !isIntensity(value.intensity)) ||
72
81
  (value.preference !== undefined && !isPreference(value.preference))
73
82
  )
74
83
  return undefined;
@@ -88,6 +97,7 @@ export function parseConfig(value: unknown): GlobalDefaults | undefined {
88
97
 
89
98
  return {
90
99
  schemaVersion: 2,
100
+ ...(value.intensity ? { intensity: value.intensity } : {}),
91
101
  ...(value.preference ? { preference: value.preference } : {}),
92
102
  ...(small ? { small } : {}),
93
103
  ...(medium ? { medium } : {}),
@@ -109,7 +119,7 @@ export function parseSessionState(value: unknown): SessionDelegateState | undefi
109
119
  "uiDesign",
110
120
  ]) ||
111
121
  value.schemaVersion !== 2 ||
112
- !isIntensity(value.intensity) ||
122
+ (value.intensity !== undefined && !isIntensity(value.intensity)) ||
113
123
  (value.preference !== undefined && !isPreference(value.preference))
114
124
  )
115
125
  return undefined;
@@ -132,7 +142,7 @@ export function parseSessionState(value: unknown): SessionDelegateState | undefi
132
142
 
133
143
  return {
134
144
  schemaVersion: 2,
135
- intensity: value.intensity,
145
+ ...(value.intensity ? { intensity: value.intensity } : {}),
136
146
  ...(value.preference ? { preference: value.preference } : {}),
137
147
  ...(small ? { small } : {}),
138
148
  ...(medium ? { medium } : {}),
@@ -218,14 +228,14 @@ export function resolveDelegateState(
218
228
  : copyModelRef(session.uiDesign);
219
229
 
220
230
  return {
221
- intensity: session.intensity,
231
+ intensity: session.intensity ?? defaults.intensity ?? "off",
222
232
  preference: session.preference ?? defaults.preference ?? "standard",
223
233
  small: copyModelRef(session.small ?? defaults.small),
224
234
  medium: copyModelRef(session.medium ?? defaults.medium),
225
235
  large: copyModelRef(session.large ?? defaults.large),
226
236
  ...(uiDesign ? { uiDesign } : {}),
227
237
  source: {
228
- intensity: "session",
238
+ intensity: sourceFor(session.intensity, defaults.intensity),
229
239
  preference: sourceFor(session.preference, defaults.preference),
230
240
  small: sourceFor(session.small, defaults.small),
231
241
  medium: sourceFor(session.medium, defaults.medium),
@@ -238,6 +248,7 @@ export function resolveDelegateState(
238
248
  export function defaultsFromEffectiveState(state: EffectiveDelegateState): GlobalDefaults {
239
249
  return {
240
250
  schemaVersion: 2,
251
+ intensity: state.intensity,
241
252
  preference: state.preference,
242
253
  ...(state.small ? { small: { ...state.small } } : {}),
243
254
  ...(state.medium ? { medium: { ...state.medium } } : {}),
package/src/index.ts CHANGED
@@ -49,14 +49,19 @@ function updateStatus(ctx: ExtensionContext, state: RuntimeState): void {
49
49
  ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("dim", statusLabel(state)));
50
50
  }
51
51
 
52
+ async function openEditor(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
53
+ await openDelegateEditor(ctx, pi);
54
+ updateStatus(ctx, await refresh(ctx));
55
+ }
56
+
52
57
  function configuredLabel(value: unknown): string {
53
58
  return value ? "configured" : "unset";
54
59
  }
55
60
 
56
- function statusText(state: RuntimeState): string {
61
+ export function statusText(state: RuntimeState): string {
57
62
  const { effective } = state;
58
63
  const details = [
59
- `${statusLabel(state)} intensity=${effective.intensity}`,
64
+ `${statusLabel(state)} intensity=${effective.intensity} (${effective.source.intensity})`,
60
65
  `preference=${effective.preference} (${effective.source.preference})`,
61
66
  `small=${configuredLabel(effective.small)} (${effective.source.small})`,
62
67
  `medium=${configuredLabel(effective.medium)} (${effective.source.medium})`,
@@ -99,7 +104,7 @@ export default function piDelegationPolicy(pi: ExtensionAPI): void {
99
104
  handler: async (args, ctx) => {
100
105
  const action = parseCommand(args);
101
106
  if (action.kind === "open") {
102
- await openDelegateEditor(ctx, pi);
107
+ await openEditor(pi, ctx);
103
108
  return;
104
109
  }
105
110
  if (action.kind === "status") {
@@ -120,9 +125,9 @@ export default function piDelegationPolicy(pi: ExtensionAPI): void {
120
125
  },
121
126
  });
122
127
 
123
- pi.registerShortcut("ctrl+shift+d", {
128
+ pi.registerShortcut("ctrl+alt+d", {
124
129
  description: "Open delegation policy",
125
- handler: async (ctx) => openDelegateEditor(ctx, pi),
130
+ handler: async (ctx) => openEditor(pi, ctx),
126
131
  });
127
132
 
128
133
  pi.on("session_start", async (_event, ctx) => {
package/src/prompt.ts CHANGED
@@ -2,18 +2,34 @@ import { hasRuntimeError, type RuntimeState } from "./runtime.ts";
2
2
  import type { ModelRef, Preference } from "./types.ts";
3
3
 
4
4
  const NORMAL_POLICY =
5
- "Delegate substantial, bounded, and independent work when doing so reduces effort without losing essential context. Keep architecture, global strategy, coordination, integration, final review, and work whose context is costly or risky to transfer in the main agent.";
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
- "Default to delegating substantial work with a clear objective and acceptance criteria. Keep global decisions, coordination, integration, final review, and work whose context is costly or risky to transfer in the main agent.";
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
+
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.
14
+
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
+
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
+
19
+ 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
+ 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
+
23
+ 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.`;
8
24
 
9
25
  function preferenceGuidance(preference: Preference): string {
10
26
  if (preference === "efficient") {
11
- return "Favor Small for routine delegated work. Medium and Large remain available when the task warrants them.";
27
+ return "Favor Small more strongly than standard. When Small can safely satisfy the acceptance criteria, choose it unless Medium provides a material advantage.";
12
28
  }
13
29
  if (preference === "intensive") {
14
- return "Favor Medium for substantial delegated work. Small and Large remain available when the task warrants them.";
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.";
15
31
  }
16
- return "Use Small for routine delegated work, Medium for planning, ambiguity, or broad synthesis, and Large only for exceptional blockers.";
32
+ return "Choose Small on a genuine Small/Medium tie; otherwise follow the role-selection policy above.";
17
33
  }
18
34
 
19
35
  function promptString(value: string): string {
@@ -42,9 +58,11 @@ export function buildDelegationPolicy(state: RuntimeState): string | undefined {
42
58
  Intensity: ${effective.intensity}.
43
59
  ${intensityPolicy}
44
60
 
61
+ ${ROLE_SELECTION_POLICY}
62
+
45
63
  Model preference: ${effective.preference}. ${preferenceGuidance(effective.preference)}
46
64
 
47
- 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 the task, difficulty, volume, and the selected model's capabilities. Do not treat thinking as persisted configuration.
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.
48
66
 
49
67
  Roles:
50
68
  - Small: ${formatReference(effective.small)}
package/src/types.ts CHANGED
@@ -24,6 +24,7 @@ export type ModelRef = {
24
24
 
25
25
  export type GlobalDefaults = {
26
26
  schemaVersion: 2;
27
+ intensity?: Intensity;
27
28
  preference?: Preference;
28
29
  small?: ModelRef;
29
30
  medium?: ModelRef;
@@ -33,7 +34,7 @@ export type GlobalDefaults = {
33
34
 
34
35
  export type SessionDelegateState = {
35
36
  schemaVersion: 2;
36
- intensity: Intensity;
37
+ intensity?: Intensity;
37
38
  preference?: Preference;
38
39
  small?: ModelRef;
39
40
  medium?: ModelRef;
@@ -52,7 +53,7 @@ export type EffectiveDelegateState = {
52
53
  large?: ModelRef;
53
54
  uiDesign?: ModelRef;
54
55
  source: {
55
- intensity: "session";
56
+ intensity: ValueSource;
56
57
  preference: ValueSource;
57
58
  small: ValueSource;
58
59
  medium: ValueSource;
@@ -73,5 +74,5 @@ export function emptyGlobalDefaults(): GlobalDefaults {
73
74
  }
74
75
 
75
76
  export function emptySessionState(): SessionDelegateState {
76
- return { schemaVersion: 2, intensity: "off" };
77
+ return { schemaVersion: 2 };
77
78
  }
package/src/ui.ts CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  type RuntimeState,
14
14
  } from "./runtime.ts";
15
15
  import {
16
- emptySessionState,
17
16
  INTENSITIES,
18
17
  MODEL_ROLES,
19
18
  PREFERENCES,
@@ -109,8 +108,13 @@ async function selectUiDesign(ctx: ExtensionContext, draft: SessionDelegateState
109
108
  }
110
109
 
111
110
  async function selectIntensity(ctx: ExtensionContext, draft: SessionDelegateState): Promise<void> {
112
- const selected = await selectOrCancel(ctx, "Delegation intensity", [...INTENSITIES]);
113
- if (selected) draft.intensity = selected as Intensity;
111
+ const selected = await selectOrCancel(ctx, "Delegation intensity", [
112
+ USE_GLOBAL_DEFAULT,
113
+ ...INTENSITIES,
114
+ ]);
115
+ if (!selected) return;
116
+ if (selected === USE_GLOBAL_DEFAULT) delete draft.intensity;
117
+ else draft.intensity = selected as Intensity;
114
118
  }
115
119
 
116
120
  async function selectPreference(ctx: ExtensionContext, draft: SessionDelegateState): Promise<void> {
@@ -194,7 +198,7 @@ export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI
194
198
  await writeConfig(getGlobalConfigPath(), defaults);
195
199
  state.global = defaults;
196
200
  state.diagnostics = [];
197
- ctx.ui.notify("Saved effective role settings as global defaults.", "info");
201
+ ctx.ui.notify("Saved effective delegation settings as global defaults.", "info");
198
202
  } catch {
199
203
  ctx.ui.notify(
200
204
  "Could not save global defaults. Session settings were not changed.",
@@ -203,7 +207,7 @@ export async function openDelegateEditor(ctx: ExtensionContext, pi: ExtensionAPI
203
207
  }
204
208
  }
205
209
  if (selected === RESET_SESSION) {
206
- draft = emptySessionState();
210
+ draft = { schemaVersion: 2, intensity: "off" };
207
211
  }
208
212
  }
209
213
  }