pi-subagents 0.14.1 → 0.15.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.15.0] - 2026-04-16
6
+
7
+ ### Added
8
+ - Added `systemPromptMode` so subagents can replace Pi's base prompt with `--system-prompt` instead of always appending with `--append-system-prompt`, shipping the core of issue `#85` from @isvlasov.
9
+ - Added `inheritProjectContext` and `inheritSkills` so child runs can keep or strip inherited project instruction files (`AGENTS.md`, `CLAUDE.md`, etc.) and Pi's discovered skills block.
10
+
11
+ ### Changed
12
+ - Builtin subagents now default to `systemPromptMode: replace`, with builtin `delegate` staying on `append`.
13
+ - Builtin agents now inherit project-level instruction files by default unless the user overrides them.
14
+ - Builtin agent prompts were rewritten for the new prompt-assembly model, and builtin `reviewer` / `context-builder` tool lists now match their documented behaviors. This rounds out the prompt-assembly work merged in PR `#92`, which closed issue `#85`. Thanks @isvlasov.
15
+
16
+ ### Fixed
17
+ - Cross-platform tests now avoid machine-specific Pi install paths, align homedir-sensitive settings discovery on Windows CI, and use deterministic async config-write failure fixtures.
18
+ - Request-level `cwd` handling is now consistent across management and execution paths. `subagent` requests that target a worktree or nested checkout now resolve project agents, project settings, and builtin agent overrides from the requested `cwd` instead of accidentally inheriting the parent session's repo. This fixes issue `#83`. Thanks @hakin19 for the report.
19
+ - Relative child `cwd` values now resolve from the already-selected request/shared `cwd` across sync runs, async/background runs, chain steps, and top-level parallel tasks. This fixes cases where values like `packages/app` were interpreted from the wrong base directory, which could break skill lookup, output paths, and child process spawning.
20
+ - Worktree parallel-mode validation now compares task-level `cwd` overrides after relative-path resolution, so equivalent paths like `.` no longer trigger false conflict errors against the shared worktree base.
21
+ - Internal TypeScript source imports in the touched runtime paths now consistently use `.ts` local specifiers, matching the repo's direct TypeScript runtime loading conventions and reducing drift between adjacent modules.
22
+
5
23
  ## [0.14.1] - 2026-04-14
6
24
 
7
25
  ### Fixed
package/README.md CHANGED
@@ -51,10 +51,44 @@ You can also override selected builtin fields without copying the whole agent. B
51
51
  - User scope: `~/.pi/agent/settings.json`
52
52
  - Project scope: `.pi/settings.json`
53
53
 
54
- Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides. In `/agents`, press `e` on a builtin to create or edit its override. Overridden builtins show badges like `[builtin+user]` or `[builtin+project]`.
54
+ Supported builtin override fields are `model`, `fallbackModels`, `thinking`, `systemPromptMode`, `inheritProjectContext`, `inheritSkills`, `skills`, `tools`, and `systemPrompt`. Project overrides beat user overrides.
55
+
56
+ **Overriding builtin defaults:**
57
+
58
+ All builtin agents inherit project instruction files (`AGENTS.md`, `CLAUDE.md`, etc.) by default. To make a builtin agent run without those project instructions:
59
+
60
+ **Via settings file** — add to `~/.pi/agent/settings.json` (user scope) or `.pi/settings.json` (project scope):
61
+ ```json
62
+ {
63
+ "subagents": {
64
+ "agentOverrides": {
65
+ "reviewer": {
66
+ "inheritProjectContext": false
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ **Via `/agents` UI** — press `e` on any builtin agent (like `reviewer`), then toggle `inheritProjectContext` to `false` and save. This creates an override that you can edit or remove later without modifying the builtin definition itself.
74
+
75
+ Overridden builtins show badges like `[builtin+user]` or `[builtin+project]` to indicate they have customizations applied.
55
76
 
56
77
  > **Note:** The `researcher` agent uses `web_search`, `fetch_content`, and `get_search_content` tools which require the [pi-web-access](https://github.com/nicobailon/pi-web-access) extension. Install it with `pi install npm:pi-web-access`.
57
78
 
79
+ ### Prompt Assembly Philosophy
80
+
81
+ Subagents are designed to be **narrow and intentional**. Custom subagents default to seeing only what you explicitly give them — not Pi's entire base prompt, not your repo's `AGENTS.md`, and not the parent's discovered skills.
82
+
83
+ This prevents subtle bugs where a "simple" code review agent starts making decisions based on project architecture rules it shouldn't know about, or a focused research agent gets distracted by tool descriptions meant for the orchestrator.
84
+
85
+ Use inheritance flags to selectively add back context when you actually want it:
86
+ - **`inheritProjectContext: true`** — Keep Pi's inherited project-instructions block from project-level files like `AGENTS.md` and `CLAUDE.md`
87
+ - **`inheritSkills: true`** — Let the child use Pi's discovered skills catalog
88
+ - **`systemPromptMode: append`** — Add the agent's prompt to Pi's base instead of replacing it
89
+
90
+ Builtin agents ship a little differently: they all inherit project instruction files by default so they follow repo-specific rules out of the box. `delegate` is still the exception for `systemPromptMode` — it stays on `append` because its job is orchestration within the parent workflow, not isolated task execution.
91
+
58
92
  **Agent frontmatter:**
59
93
 
60
94
  ```yaml
@@ -66,6 +100,9 @@ extensions: # absent=all, empty=none, csv=allowlist
66
100
  model: claude-haiku-4-5
67
101
  fallbackModels: openai/gpt-5-mini, anthropic/claude-sonnet-4 # optional ordered fallbacks
68
102
  thinking: high # off, minimal, low, medium, high, xhigh
103
+ systemPromptMode: replace # replace by default, except builtin delegate
104
+ inheritProjectContext: false # custom agents default false; builtins opt into true
105
+ inheritSkills: false # strip Pi's discovered skills section
69
106
  skill: safe-bash, chrome-devtools # comma-separated skills to inject
70
107
  output: context.md # writes to {chain_dir}/context.md
71
108
  defaultReads: context.md # comma-separated files to read
@@ -81,10 +118,51 @@ The `thinking` field sets a default extended thinking level for the agent. At ru
81
118
 
82
119
  `fallbackModels` is an optional ordered list of backup models to try when the primary model fails with a provider/model-style error such as quota, auth, timeout, or provider/model unavailable. In markdown frontmatter, declare it as a comma-separated string. In management `config` objects, you can pass either a comma-separated string or a string array.
83
120
 
121
+ `systemPromptMode` — How the agent markdown body is passed to Pi:
122
+ - **`replace`** (default) — The agent's markdown body becomes the system prompt. Clean slate, no Pi base prompt baggage.
123
+ - **`append`** — The agent's prompt is appended to Pi's normal base prompt. Use this when you want Pi's full capabilities plus your extra instructions.
124
+
125
+ `inheritProjectContext` — Whether the child keeps Pi's inherited project-instructions block, built from project-level instruction files like `AGENTS.md` and `CLAUDE.md`:
126
+ - **`false`** (default) — Strip those inherited project-level instructions from the child's final system prompt. This does not remove repo access, cwd, tools, or the task itself; it only removes those inherited instructions.
127
+ - **`true`** — Keep those inherited project-level instructions. Use for specialists that should follow project-specific constraints and conventions.
128
+
129
+ `inheritSkills` — Whether the child keeps Pi's discovered skills section:
130
+ - **`false`** (default) — Skills catalog is stripped. Good for focused agents that shouldn't browse unrelated skills.
131
+ - **`true`** — Child sees the full skills list. Use for general-purpose assistants that might need varied tools.
132
+
133
+ The `skills` field still works independently — it injects specific skills directly into the agent prompt regardless of `inheritSkills`.
134
+
135
+ **Common Recipes**
136
+
137
+ | Goal | `systemPromptMode` | `inheritProjectContext` | `inheritSkills` |
138
+ |------|-------------------|------------------------|-----------------|
139
+ | Fully isolated specialist (custom-agent default) | `replace` | `false` | `false` |
140
+ | Specialist that should follow project instruction files | `replace` | `true` | `false` |
141
+ | Pi-plus-extensions | `append` | `true` | `true` |
142
+
143
+ - **Security auditor**: Fully isolated so it objectively checks for vulnerabilities without being biased by project conventions.
144
+ - **Architecture planner**: Repo-aware so it respects `AGENTS.md` constraints when making design decisions.
145
+ - **Generic helper**: Append mode with full inheritance so it behaves like a slightly-customized Pi.
146
+
84
147
  Fallback resolution follows the same conservative model lookup as normal execution. Explicit `provider/model` values are used as-is. Bare model IDs first prefer the current session provider when that provider actually exposes the model, then fall back to a unique registry match. If a bare ID is still ambiguous, it stays bare.
85
148
 
86
149
  Fallback is only used for provider/model availability failures. Ordinary task failures such as bad `bash` commands, missing files, or other tool/runtime errors do not trigger a model hop.
87
150
 
151
+ **Tool selection semantics**
152
+
153
+ The `tools` field controls builtin-tool allowlisting and a couple of related extension behaviors:
154
+
155
+ - `tools` **omitted** → `pi-subagents` does not pass `--tools`, so the child gets Pi's normal default builtin tools.
156
+ - `tools` **present** → listed builtin tool names become an explicit allowlist passed via `--tools`.
157
+ - `mcp:...` entries are split out of `tools` and forwarded as direct MCP tool selections.
158
+ - Path-like entries in `tools` (extension paths or file paths ending in `.ts` / `.js`) are treated as tool-extension paths, not builtin tool names.
159
+
160
+ This means:
161
+
162
+ - `tools` omitted + `extensions` omitted → child gets Pi's normal builtin tools and normal extension set.
163
+ - `tools: mcp:chrome-devtools` → child still gets Pi's default builtin tools, plus direct MCP tools from `chrome-devtools`.
164
+ - `tools: read, bash, mcp:chrome-devtools` → child is restricted to `read` and `bash` for builtin tools, plus direct MCP tools from `chrome-devtools`.
165
+
88
166
  **Extension sandboxing**
89
167
 
90
168
  Use `extensions` in frontmatter to control which extensions a subagent can access:
@@ -229,7 +307,7 @@ Press **Ctrl+Shift+A** or type `/agents` to open the Agents Manager overlay —
229
307
  |--------|-------------|
230
308
  | List | Browse all agents and chains with search/filter, scope badges, chain badges |
231
309
  | Detail | View resolved prompt, frontmatter fields, recent run history, and active builtin override path |
232
- | Edit | Edit fields with specialized pickers (model, thinking, skills, prompt editor) |
310
+ | Edit | Edit fields with specialized pickers and toggles (model, thinking, prompt mode, inherited context, inherited skills, prompt editor) |
233
311
  | Chain Detail | View chain steps with flow visualization and dependency map |
234
312
  | Parallel Builder | Build parallel execution slots, add same agent multiple times, per-slot task overrides |
235
313
  | Task Input | Enter task and launch with optional skip-clarify toggle |
@@ -573,6 +651,9 @@ Agent definitions are not loaded into LLM context by default. Management actions
573
651
  description: "Scans codebases for patterns and issues",
574
652
  scope: "user",
575
653
  systemPrompt: "You are a code scout...",
654
+ systemPromptMode: "replace",
655
+ inheritProjectContext: false,
656
+ inheritSkills: false,
576
657
  model: "anthropic/claude-sonnet-4",
577
658
  fallbackModels: ["openai/gpt-5-mini", "anthropic/claude-haiku-4-5"],
578
659
  tools: "read, bash, mcp:github/search_repositories",
@@ -613,7 +694,7 @@ Agent definitions are not loaded into LLM context by default. Management actions
613
694
  Notes:
614
695
  - `create` uses `config.scope` (`"user"` or `"project"`), not `agentScope`.
615
696
  - `update`/`delete` use `agentScope` only for scope disambiguation when the same name exists in both scopes.
616
- - Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, `maxSubagentDepth` maps directly to agent frontmatter, `fallbackModels` maps directly to agent frontmatter, and `tools` supports `mcp:` entries that map to direct MCP tools.
697
+ - Agent config mapping: `reads -> defaultReads`, `progress -> defaultProgress`, `extensions` controls extension sandboxing, `maxSubagentDepth`, `fallbackModels`, `systemPromptMode`, `inheritProjectContext`, and `inheritSkills` map directly to agent frontmatter, and `tools` supports `mcp:` entries that map to direct MCP tools.
617
698
  - To clear any optional field, set it to `false` or `""` (e.g., `{ model: false }` or `{ skills: "" }`). Both work for all string-typed fields.
618
699
 
619
700
  ## Parameters
@@ -624,7 +705,7 @@ Notes:
624
705
  | `task` | string | - | Task string (single mode) |
625
706
  | `action` | string | - | Management action: `list`, `get`, `create`, `update`, `delete` |
626
707
  | `chainName` | string | - | Chain name for management get/update/delete |
627
- | `config` | object | - | Agent or chain config for management create/update. Agent configs also accept `fallbackModels` (comma-separated string or string array). |
708
+ | `config` | object | - | Agent or chain config for management create/update. Agent configs also accept `fallbackModels` (comma-separated string or string array), `systemPromptMode` (`append` or `replace`), `inheritProjectContext` (boolean), and `inheritSkills` (boolean). |
628
709
  | `output` | `string \| false` | agent default | Override output file for single agent (absolute path as-is, relative path resolved against cwd) |
629
710
  | `skill` | `string \| string[] \| false` | agent default | Override skills (comma-separated string, array, or false to disable) |
630
711
  | `model` | string | agent default | Override model for single agent |
@@ -8,6 +8,9 @@ import {
8
8
  type AgentSource,
9
9
  type ChainConfig,
10
10
  type ChainStepConfig,
11
+ defaultInheritProjectContext,
12
+ defaultInheritSkills,
13
+ defaultSystemPromptMode,
11
14
  discoverAgentsAll,
12
15
  } from "./agents.ts";
13
16
  import { serializeAgent } from "./agent-serializer.ts";
@@ -244,6 +247,18 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
244
247
  else if (typeof cfg.thinking === "string") target.thinking = cfg.thinking.trim() || undefined;
245
248
  else return "config.thinking must be a string or false when provided.";
246
249
  }
250
+ if (hasKey(cfg, "systemPromptMode")) {
251
+ if (cfg.systemPromptMode === "append" || cfg.systemPromptMode === "replace") target.systemPromptMode = cfg.systemPromptMode;
252
+ else return "config.systemPromptMode must be 'append' or 'replace' when provided.";
253
+ }
254
+ if (hasKey(cfg, "inheritProjectContext")) {
255
+ if (typeof cfg.inheritProjectContext !== "boolean") return "config.inheritProjectContext must be a boolean when provided.";
256
+ target.inheritProjectContext = cfg.inheritProjectContext;
257
+ }
258
+ if (hasKey(cfg, "inheritSkills")) {
259
+ if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided.";
260
+ target.inheritSkills = cfg.inheritSkills;
261
+ }
247
262
  if (hasKey(cfg, "output")) {
248
263
  if (cfg.output === false || cfg.output === "") target.output = undefined;
249
264
  else if (typeof cfg.output === "string") target.output = cfg.output;
@@ -320,6 +335,9 @@ export function formatAgentDetail(agent: AgentConfig): string {
320
335
  if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`);
321
336
  if (tools.length) lines.push(`Tools: ${tools.join(", ")}`);
322
337
  if (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(", ")}`);
338
+ lines.push(`System prompt mode: ${agent.systemPromptMode}`);
339
+ lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
340
+ lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
323
341
  if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`);
324
342
  if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`);
325
343
  if (agent.output) lines.push(`Output: ${agent.output}`);
@@ -418,7 +436,16 @@ export function handleCreate(params: ManagementParams, ctx: ManagementContext):
418
436
  warnings.push(...chainStepWarnings(ctx, chain.steps));
419
437
  return result([`Created chain '${name}' at ${targetPath}.`, ...warnings].join("\n"));
420
438
  }
421
- const agent: AgentConfig = { name, description: cfg.description.trim(), source: scope, filePath: targetPath, systemPrompt: "" };
439
+ const agent: AgentConfig = {
440
+ name,
441
+ description: cfg.description.trim(),
442
+ source: scope,
443
+ filePath: targetPath,
444
+ systemPrompt: "",
445
+ systemPromptMode: defaultSystemPromptMode(name),
446
+ inheritProjectContext: defaultInheritProjectContext(name),
447
+ inheritSkills: defaultInheritSkills(),
448
+ };
422
449
  const applyError = applyAgentConfig(agent, cfg);
423
450
  if (applyError) return result(applyError, true);
424
451
  const mw = modelWarning(ctx, agent.model);
@@ -446,7 +473,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
446
473
  const target = targetOrError;
447
474
  const updated: AgentConfig = { ...target };
448
475
  const oldName = target.name;
449
- // Validate all fields before any filesystem mutation
450
476
  if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
451
477
  if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
452
478
  let newName: string | undefined;
@@ -456,7 +482,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
456
482
  }
457
483
  const applyError = applyAgentConfig(updated, cfg);
458
484
  if (applyError) return result(applyError, true);
459
- // Apply name/description (validated above)
460
485
  if (newName !== undefined) updated.name = newName;
461
486
  if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
462
487
  if (hasKey(cfg, "model")) {
@@ -471,7 +496,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
471
496
  const sw = skillsWarning(ctx.cwd, updated.skills);
472
497
  if (sw) warnings.push(sw);
473
498
  }
474
- // Filesystem mutations last
475
499
  if (updated.name !== oldName) {
476
500
  const renamed = renamePath("agent", target.filePath, updated.name, target.source, ctx.cwd);
477
501
  if (renamed.error) return result(renamed.error, true);
@@ -493,7 +517,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
493
517
  const target = targetOrError;
494
518
  const updated: ChainConfig = { ...target, steps: [...target.steps] };
495
519
  const oldName = target.name;
496
- // Validate all fields before any filesystem mutation
497
520
  if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true);
498
521
  if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true);
499
522
  let newName: string | undefined;
@@ -507,7 +530,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
507
530
  if (parsed.error) return result(parsed.error, true);
508
531
  parsedSteps = parsed.steps!;
509
532
  }
510
- // Apply validated changes to in-memory object
511
533
  if (newName !== undefined) updated.name = newName;
512
534
  if (hasKey(cfg, "description")) updated.description = (cfg.description as string).trim();
513
535
  if (parsedSteps) {
@@ -516,7 +538,6 @@ export function handleUpdate(params: ManagementParams, ctx: ManagementContext):
516
538
  if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`);
517
539
  warnings.push(...chainStepWarnings(ctx, updated.steps));
518
540
  }
519
- // Filesystem mutations last
520
541
  if (updated.name !== oldName) {
521
542
  const renamed = renamePath("chain", target.filePath, updated.name, target.source, ctx.cwd);
522
543
  if (renamed.error) return result(renamed.error, true);
@@ -27,7 +27,7 @@ function renderFieldLine(
27
27
  width: number,
28
28
  theme: Theme,
29
29
  ): string {
30
- const labelWidth = 10;
30
+ const labelWidth = 12;
31
31
  const labelText = theme.fg("dim", pad(label, labelWidth));
32
32
  const available = Math.max(0, width - labelWidth);
33
33
  return `${labelText}${truncateToWidth(value, available)}`;
@@ -61,6 +61,9 @@ function buildDetailLines(
61
61
  const maxSubagentDepth = agent.maxSubagentDepth !== undefined ? String(agent.maxSubagentDepth) : "(default)";
62
62
 
63
63
  lines.push(renderFieldLine("Model:", agent.model ?? "default", contentWidth, theme));
64
+ lines.push(renderFieldLine("Prompt mode:", agent.systemPromptMode, contentWidth, theme));
65
+ lines.push(renderFieldLine("Project ctx:", agent.inheritProjectContext ? "on" : "off", contentWidth, theme));
66
+ lines.push(renderFieldLine("Skills ctx:", agent.inheritSkills ? "on" : "off", contentWidth, theme));
64
67
  if (agent.override) {
65
68
  const overrideLabel = `${agent.override.scope} · ${formatPath(agent.override.path)}`;
66
69
  lines.push(renderFieldLine("Override:", overrideLabel, contentWidth, theme));
@@ -1,6 +1,6 @@
1
1
  import type { Theme } from "@mariozechner/pi-coding-agent";
2
2
  import { matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
3
- import type { AgentConfig, BuiltinAgentOverrideBase } from "./agents.ts";
3
+ import { defaultSystemPromptMode, type AgentConfig, type BuiltinAgentOverrideBase } from "./agents.ts";
4
4
  import { createEditorState, ensureCursorVisible, getCursorDisplayPos, handleEditorInput, renderEditor, wrapText } from "./text-editor.ts";
5
5
  import type { TextEditorState } from "./text-editor.ts";
6
6
  import { pad, row, renderHeader, renderFooter, formatScrollInfo } from "./render-helpers.ts";
@@ -26,7 +26,7 @@ export interface CreateEditStateOptions {
26
26
  }
27
27
 
28
28
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
29
- const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
29
+ const FIELD_ORDER = ["name", "description", "model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "extensions", "skills", "output", "reads", "progress", "interactive", "prompt"] as const;
30
30
  type ThinkingLevel = typeof THINKING_LEVELS[number];
31
31
  const PROMPT_VIEWPORT_HEIGHT = 16;
32
32
  const MODEL_SELECTOR_HEIGHT = 10;
@@ -45,6 +45,9 @@ function fieldValueMatchesBase(field: EditField, state: EditState): boolean {
45
45
  case "model": return state.draft.model === base.model;
46
46
  case "fallbackModels": return arraysEqual(state.draft.fallbackModels, base.fallbackModels);
47
47
  case "thinking": return state.draft.thinking === base.thinking;
48
+ case "systemPromptMode": return state.draft.systemPromptMode === base.systemPromptMode;
49
+ case "inheritProjectContext": return state.draft.inheritProjectContext === base.inheritProjectContext;
50
+ case "inheritSkills": return state.draft.inheritSkills === base.inheritSkills;
48
51
  case "tools": return arraysEqual(toolList(state.draft), toolList(base));
49
52
  case "skills": return arraysEqual(state.draft.skills, base.skills);
50
53
  case "prompt": return state.draft.systemPrompt === base.systemPrompt;
@@ -59,6 +62,9 @@ function resetFieldToBase(field: EditField, state: EditState): void {
59
62
  case "model": state.draft.model = base.model; break;
60
63
  case "fallbackModels": state.draft.fallbackModels = base.fallbackModels ? [...base.fallbackModels] : undefined; break;
61
64
  case "thinking": state.draft.thinking = base.thinking; break;
65
+ case "systemPromptMode": state.draft.systemPromptMode = base.systemPromptMode; break;
66
+ case "inheritProjectContext": state.draft.inheritProjectContext = base.inheritProjectContext; break;
67
+ case "inheritSkills": state.draft.inheritSkills = base.inheritSkills; break;
62
68
  case "tools": state.draft.tools = base.tools ? [...base.tools] : undefined; state.draft.mcpDirectTools = base.mcpDirectTools ? [...base.mcpDirectTools] : undefined; break;
63
69
  case "skills": state.draft.skills = base.skills ? [...base.skills] : undefined; break;
64
70
  case "prompt": state.draft.systemPrompt = base.systemPrompt; state.promptEditor = createEditorState(base.systemPrompt); break;
@@ -83,6 +89,9 @@ function renderFieldValue(field: EditField, state: EditState): string {
83
89
  case "model": return draft.model ?? "default";
84
90
  case "fallbackModels": return draft.fallbackModels && draft.fallbackModels.length > 0 ? draft.fallbackModels.join(", ") : "";
85
91
  case "thinking": return draft.thinking ?? "off";
92
+ case "systemPromptMode": return draft.systemPromptMode ?? defaultSystemPromptMode(draft.name);
93
+ case "inheritProjectContext": return draft.inheritProjectContext ? "on" : "off";
94
+ case "inheritSkills": return draft.inheritSkills ? "on" : "off";
86
95
  case "tools": return formatTools(draft);
87
96
  case "extensions": return draft.extensions !== undefined ? (draft.extensions.length > 0 ? draft.extensions.join(", ") : "") : "(all)";
88
97
  case "skills": return draft.skills && draft.skills.length > 0 ? draft.skills.join(", ") : "";
@@ -101,12 +110,28 @@ function applyFieldValue(field: EditField, state: EditState, value: string): voi
101
110
  case "description": draft.description = value.trim(); break;
102
111
  case "model": draft.model = value.trim() || undefined; break;
103
112
  case "fallbackModels": draft.fallbackModels = parseCommaList(value); break;
113
+ case "systemPromptMode": {
114
+ const trimmed = value.trim();
115
+ if (trimmed === "") {
116
+ draft.systemPromptMode = defaultSystemPromptMode(draft.name);
117
+ break;
118
+ }
119
+ if (trimmed === "append" || trimmed === "replace") {
120
+ draft.systemPromptMode = trimmed;
121
+ }
122
+ break;
123
+ }
104
124
  case "tools": { const parsed = parseTools(value); draft.tools = parsed.tools; draft.mcpDirectTools = parsed.mcp; break; }
105
125
  case "extensions": { const trimmed = value.trim(); draft.extensions = trimmed === "(all)" ? undefined : parseCommaList(trimmed) ?? []; break; }
106
126
  case "skills": draft.skills = parseCommaList(value); break;
107
127
  case "output": { const trimmed = value.trim(); draft.output = trimmed.length > 0 ? trimmed : undefined; break; }
108
128
  case "reads": draft.defaultReads = parseCommaList(value); break;
109
- case "progress": case "interactive": case "prompt": break;
129
+ case "inheritProjectContext":
130
+ case "inheritSkills":
131
+ case "progress":
132
+ case "interactive":
133
+ case "prompt":
134
+ break;
110
135
  }
111
136
  }
112
137
 
@@ -231,13 +256,19 @@ export function handleEditInput(screen: EditScreen, state: EditState, data: stri
231
256
  if (data === "m") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
232
257
  if (data === "t") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
233
258
  if (data === "s") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
234
- if (data === " " && (field === "progress" || field === "interactive")) { if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress; if (field === "interactive") state.draft.interactive = !state.draft.interactive; return; }
259
+ if (data === " " && (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive")) {
260
+ if (field === "inheritProjectContext") state.draft.inheritProjectContext = !state.draft.inheritProjectContext;
261
+ if (field === "inheritSkills") state.draft.inheritSkills = !state.draft.inheritSkills;
262
+ if (field === "progress") state.draft.defaultProgress = !state.draft.defaultProgress;
263
+ if (field === "interactive") state.draft.interactive = !state.draft.interactive;
264
+ return;
265
+ }
235
266
  if (matchesKey(data, "return")) {
236
267
  if (field === "model") { openModelPicker(state, models); return { nextScreen: "edit-field" }; }
237
268
  if (field === "thinking") { openThinkingPicker(state); return { nextScreen: "edit-field" }; }
238
269
  if (field === "skills") { openSkillPicker(state, skills); return { nextScreen: "edit-field" }; }
239
270
  if (field === "prompt") { state.promptEditor = createEditorState(state.draft.systemPrompt ?? ""); return { nextScreen: "edit-prompt" }; }
240
- if (field === "progress" || field === "interactive") return;
271
+ if (field === "inheritProjectContext" || field === "inheritSkills" || field === "progress" || field === "interactive") return;
241
272
  state.fieldMode = "text"; state.fieldEditor = createEditorState(renderFieldValue(field, state)); return { nextScreen: "edit-field" };
242
273
  }
243
274
  return;
@@ -304,8 +335,17 @@ export function renderEdit(screen: EditScreen, state: EditState, width: number,
304
335
  for (let i = 0; i < state.fields.length; i++) {
305
336
  const field = state.fields[i]!; if (field === "prompt") break;
306
337
  const isFocused = i === state.fieldIndex; const prefix = isFocused ? theme.fg("accent", "▸ ") : " ";
307
- const rawLabel = pad(`${field[0]!.toUpperCase()}${field.slice(1)}:`, labelWidth);
338
+ const fieldLabel = field === "systemPromptMode"
339
+ ? "Prompt Mode"
340
+ : field === "inheritProjectContext"
341
+ ? "Project Ctx"
342
+ : field === "inheritSkills"
343
+ ? "Skills Ctx"
344
+ : `${field[0]!.toUpperCase()}${field.slice(1)}`;
345
+ const rawLabel = pad(`${fieldLabel}:`, labelWidth);
308
346
  const labelText = state.overrideBase && !fieldValueMatchesBase(field, state) ? theme.fg("accent", rawLabel) : rawLabel; let valueText = renderFieldValue(field, state);
347
+ if (field === "inheritProjectContext") { const toggle = state.draft.inheritProjectContext ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritProjectContext ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
348
+ if (field === "inheritSkills") { const toggle = state.draft.inheritSkills ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.inheritSkills ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
309
349
  if (field === "progress") { const toggle = state.draft.defaultProgress ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.defaultProgress ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
310
350
  if (field === "interactive") { const toggle = state.draft.interactive ? theme.fg("success", "[x]") : "[ ]"; valueText = `${toggle} ${state.draft.interactive ? "on" : "off"}`; lines.push(row(` ${prefix}${labelText} ${pad(truncateToWidth(valueText, valueWidth), valueWidth)}`, width, theme)); continue; }
311
351
  let displayValue = truncateToWidth(valueText, valueWidth);
package/agent-manager.ts CHANGED
@@ -5,6 +5,9 @@ import type { Component, TUI } from "@mariozechner/pi-tui";
5
5
  import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
6
6
  import {
7
7
  buildBuiltinOverrideConfig,
8
+ defaultInheritProjectContext,
9
+ defaultInheritSkills,
10
+ defaultSystemPromptMode,
8
11
  discoverAgentsAll,
9
12
  removeBuiltinAgentOverride,
10
13
  saveBuiltinAgentOverride,
@@ -40,7 +43,7 @@ interface NameInputState { mode: "new-agent" | "clone-agent" | "clone-chain" | "
40
43
  interface StatusMessage { text: string; type: "error" | "info"; }
41
44
  interface OverrideScopeState { selectedScope: "user" | "project"; allowProject: boolean; }
42
45
 
43
- const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "tools", "skills", "prompt"];
46
+ const BUILTIN_OVERRIDE_FIELDS: EditField[] = ["model", "fallbackModels", "thinking", "systemPromptMode", "inheritProjectContext", "inheritSkills", "tools", "skills", "prompt"];
44
47
 
45
48
  function cloneConfig(config: AgentConfig): AgentConfig {
46
49
  return {
@@ -130,6 +133,9 @@ export class AgentManagerComponent implements Component {
130
133
  model: entry.config.model,
131
134
  fallbackModels: entry.config.fallbackModels ? [...entry.config.fallbackModels] : undefined,
132
135
  thinking: entry.config.thinking,
136
+ systemPromptMode: entry.config.systemPromptMode,
137
+ inheritProjectContext: entry.config.inheritProjectContext,
138
+ inheritSkills: entry.config.inheritSkills,
133
139
  systemPrompt: entry.config.systemPrompt,
134
140
  skills: entry.config.skills ? [...entry.config.skills] : undefined,
135
141
  tools: entry.config.tools ? [...entry.config.tools] : undefined,
@@ -233,6 +239,16 @@ export class AgentManagerComponent implements Component {
233
239
  filePath = path.join(dir, `${edit.draft.name}.md`);
234
240
  if (fs.existsSync(filePath)) { edit.error = "An agent with that name already exists."; return false; }
235
241
  fs.mkdirSync(dir, { recursive: true });
242
+ } else if (edit.draft.name !== entry.config.name) {
243
+ const nextPath = path.join(path.dirname(filePath), `${edit.draft.name}.md`);
244
+ if (nextPath !== filePath && fs.existsSync(nextPath)) {
245
+ edit.error = "An agent with that name already exists.";
246
+ return false;
247
+ }
248
+ if (nextPath !== filePath) {
249
+ fs.renameSync(filePath, nextPath);
250
+ filePath = nextPath;
251
+ }
236
252
  }
237
253
  try { const toSave: AgentConfig = { ...edit.draft, filePath }; fs.writeFileSync(filePath, serializeAgent(toSave), "utf-8"); entry.config = cloneConfig(toSave); entry.isNew = false; edit.error = undefined; return true; }
238
254
  catch (err) { edit.error = err instanceof Error ? err.message : "Failed to save agent."; return false; }
@@ -349,7 +365,17 @@ export class AgentManagerComponent implements Component {
349
365
  baseConfig = cloneConfig(sourceEntry.config);
350
366
  } else {
351
367
  const templateConfig = state.template?.config ?? {};
352
- baseConfig = { name, description: "Describe this agent", systemPrompt: "", source: state.scope, filePath: "", ...templateConfig };
368
+ baseConfig = {
369
+ name,
370
+ description: "Describe this agent",
371
+ systemPrompt: "",
372
+ systemPromptMode: defaultSystemPromptMode(name),
373
+ inheritProjectContext: defaultInheritProjectContext(name),
374
+ inheritSkills: defaultInheritSkills(),
375
+ source: state.scope,
376
+ filePath: "",
377
+ ...templateConfig,
378
+ };
353
379
  }
354
380
  const dir = state.scope === "project" ? this.agentData.projectDir : this.agentData.userDir;
355
381
  if (!dir) { state.error = "Project agents directory not found."; this.tui.requestRender(); return; }
@@ -8,6 +8,9 @@ export const KNOWN_FIELDS = new Set([
8
8
  "model",
9
9
  "fallbackModels",
10
10
  "thinking",
11
+ "systemPromptMode",
12
+ "inheritProjectContext",
13
+ "inheritSkills",
11
14
  "skill",
12
15
  "skills",
13
16
  "extensions",
@@ -40,6 +43,9 @@ export function serializeAgent(config: AgentConfig): string {
40
43
  const fallbackModelsValue = joinComma(config.fallbackModels);
41
44
  if (fallbackModelsValue) lines.push(`fallbackModels: ${fallbackModelsValue}`);
42
45
  if (config.thinking && config.thinking !== "off") lines.push(`thinking: ${config.thinking}`);
46
+ lines.push(`systemPromptMode: ${config.systemPromptMode}`);
47
+ lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`);
48
+ lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`);
43
49
 
44
50
  const skillsValue = joinComma(config.skills);
45
51
  if (skillsValue) lines.push(`skills: ${skillsValue}`);
@@ -1,38 +1,36 @@
1
1
  ---
2
2
  name: context-builder
3
3
  description: Analyzes requirements and codebase, generates context and meta-prompt
4
- tools: read, grep, find, ls, bash, web_search
4
+ tools: read, grep, find, ls, bash, write, web_search
5
5
  model: claude-sonnet-4-6
6
+ systemPromptMode: replace
7
+ inheritProjectContext: true
8
+ inheritSkills: false
6
9
  output: context.md
7
10
  ---
8
11
 
9
- You analyze user requirements against a codebase to build comprehensive context.
12
+ You are a requirements-to-context subagent.
10
13
 
11
- Given a user request (prose, user stories, requirements), you will:
14
+ Analyze the user request against the codebase, gather the minimum high-value context, and produce structured handoff material for planning.
12
15
 
13
- 1. **Analyze the request** - Understand what the user wants to build
14
- 2. **Search the codebase** - Find all relevant files, patterns, dependencies
15
- 3. **Research if needed** - Look up APIs, libraries, best practices online
16
- 4. **Generate output files** - You'll receive instructions about where to write
16
+ Working rules:
17
+ - Read the request carefully before touching the codebase.
18
+ - Search the codebase for relevant files, patterns, dependencies, and constraints.
19
+ - Use `web_search` only when the task depends on external APIs, libraries, or current best practices.
20
+ - Write the requested output files clearly and concretely.
21
+ - Prefer distilled, high-signal context over exhaustive dumps.
17
22
 
18
- When running in a chain, generate two files in the specified chain directory:
23
+ When running in a chain, expect to generate two files in the chain directory:
19
24
 
20
- **context.md** - Code context:
21
- # Code Context
22
- ## Relevant Files
23
- [files with line numbers and snippets]
24
- ## Patterns Found
25
- [existing patterns to follow]
26
- ## Dependencies
27
- [libraries, APIs involved]
25
+ `context.md`
26
+ - relevant files with line numbers and key snippets
27
+ - important patterns already used in the codebase
28
+ - dependencies, constraints, and implementation risks
28
29
 
29
- **meta-prompt.md** - Optimized instructions for planner:
30
- # Meta-Prompt for Planning
31
- ## Requirements Summary
32
- [distilled requirements]
33
- ## Technical Constraints
34
- [must-haves, limitations]
35
- ## Suggested Approach
36
- [recommended implementation strategy]
37
- ## Questions Resolved
38
- [decisions made during analysis]
30
+ `meta-prompt.md`
31
+ - distilled requirements summary
32
+ - technical constraints
33
+ - suggested implementation approach
34
+ - resolved questions and assumptions
35
+
36
+ The goal is to hand the planner exactly enough code and requirement context to produce a strong implementation plan without having to rediscover the same ground.
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: delegate
3
3
  description: Lightweight subagent that inherits the parent model with no default reads
4
+ systemPromptMode: append
5
+ inheritProjectContext: true
6
+ inheritSkills: false
4
7
  ---
5
8
 
6
- You are a delegated agent. Execute the assigned task using your tools. Be direct and efficient.
9
+ You are a delegated agent. Execute the assigned task using the provided tools. Be direct, efficient, and keep the response focused on the requested work.
package/agents/planner.md CHANGED
@@ -4,43 +4,49 @@ description: Creates implementation plans from context and requirements
4
4
  tools: read, grep, find, ls, write
5
5
  model: claude-opus-4-6
6
6
  thinking: high
7
+ systemPromptMode: replace
8
+ inheritProjectContext: true
9
+ inheritSkills: false
7
10
  output: plan.md
8
11
  defaultReads: context.md
9
12
  ---
10
13
 
11
- You are a planning specialist. You receive context and requirements, then produce a clear implementation plan.
14
+ You are a planning subagent.
12
15
 
13
- You must NOT make any changes. Only read, analyze, and plan.
16
+ Your job is to turn requirements and code context into a concrete implementation plan. Do not make code changes. Read, analyze, and write the plan only.
14
17
 
15
- When running in a chain, you'll receive instructions about which files to read and where to write your output.
18
+ Working rules:
19
+ - Read the provided context before planning.
20
+ - Read any additional code you need in order to make the plan concrete.
21
+ - Name exact files whenever you can.
22
+ - Prefer small, ordered, actionable tasks over vague phases.
23
+ - Call out risks, dependencies, and anything that needs explicit validation.
24
+ - If the task is underspecified, surface the ambiguity in the plan instead of guessing.
16
25
 
17
- Output format (plan.md):
26
+ Output format (`plan.md`):
18
27
 
19
28
  # Implementation Plan
20
29
 
21
30
  ## Goal
22
- One sentence summary of what needs to be done.
31
+ One sentence summary of the outcome.
23
32
 
24
33
  ## Tasks
25
- Numbered steps, each small and actionable:
34
+ Numbered steps, each small and actionable.
26
35
  1. **Task 1**: Description
27
36
  - File: `path/to/file.ts`
28
- - Changes: What to modify
29
- - Acceptance: How to verify
30
-
31
- 2. **Task 2**: Description
32
- ...
37
+ - Changes: what to modify
38
+ - Acceptance: how to verify
33
39
 
34
40
  ## Files to Modify
35
- - `path/to/file.ts` - what changes
41
+ - `path/to/file.ts` - what changes there
36
42
 
37
- ## New Files (if any)
43
+ ## New Files
38
44
  - `path/to/new.ts` - purpose
39
45
 
40
46
  ## Dependencies
41
47
  Which tasks depend on others.
42
48
 
43
49
  ## Risks
44
- Anything to watch out for.
50
+ Anything likely to go wrong, need clarification, or need careful verification.
45
51
 
46
- Keep the plan concrete. The worker agent will execute it.
52
+ Keep the plan concrete. Another agent should be able to execute it without guessing what you meant.