@gotgenes/pi-subagents 12.1.0 → 13.1.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 +24 -0
- package/dist/public.d.ts +1 -3
- package/docs/architecture/architecture.md +86 -57
- package/docs/plans/0264-remove-extension-lifecycle-control.md +275 -0
- package/docs/plans/0265-born-complete-subagent-session.md +330 -0
- package/docs/retro/0264-remove-extension-lifecycle-control.md +89 -0
- package/docs/retro/0265-born-complete-subagent-session.md +58 -0
- package/package.json +1 -1
- package/src/config/agent-types.ts +0 -2
- package/src/config/custom-agents.ts +0 -30
- package/src/config/default-agents.ts +1 -7
- package/src/config/invocation-config.ts +0 -3
- package/src/index.ts +3 -5
- package/src/lifecycle/agent-manager.ts +9 -10
- package/src/lifecycle/agent.ts +56 -55
- package/src/lifecycle/create-subagent-session.ts +242 -0
- package/src/lifecycle/subagent-session.ts +204 -0
- package/src/lifecycle/turn-limits.ts +13 -0
- package/src/runtime.ts +1 -1
- package/src/service/service-adapter.ts +0 -1
- package/src/service/service.ts +0 -1
- package/src/session/conversation.ts +49 -0
- package/src/session/prompts.ts +2 -23
- package/src/session/session-config.ts +10 -45
- package/src/settings.ts +1 -1
- package/src/tools/agent-tool.ts +0 -5
- package/src/tools/background-spawner.ts +0 -1
- package/src/tools/foreground-runner.ts +0 -1
- package/src/tools/get-result-tool.ts +1 -1
- package/src/tools/spawn-config.ts +1 -5
- package/src/types.ts +0 -7
- package/src/ui/agent-config-editor.ts +0 -5
- package/src/ui/agent-creation-wizard.ts +0 -4
- package/src/ui/display.ts +1 -2
- package/src/lifecycle/agent-runner.ts +0 -472
- package/src/lifecycle/execution-state.ts +0 -17
- package/src/session/safe-fs.ts +0 -45
- package/src/session/skill-loader.ts +0 -104
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* session-config.ts — Pure configuration assembler for agent sessions.
|
|
3
3
|
*
|
|
4
|
-
* `assembleSessionConfig()` is the pure core
|
|
5
|
-
* It accepts resolved inputs (agent type, narrow
|
|
6
|
-
*
|
|
7
|
-
* importing or constructing any Pi SDK types.
|
|
4
|
+
* `assembleSessionConfig()` is the pure assembly core called by
|
|
5
|
+
* `createSubagentSession()`. It accepts resolved inputs (agent type, narrow
|
|
6
|
+
* context, run options, env info) and returns everything the factory needs to
|
|
7
|
+
* create the SDK session — without importing or constructing any Pi SDK types.
|
|
8
8
|
*
|
|
9
9
|
* The only async IO in the assembly phase (`detectEnv`) is handled by the caller
|
|
10
10
|
* before invoking this function, keeping the assembler synchronous.
|
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
14
14
|
import type { EnvInfo } from "#src/session/env";
|
|
15
|
-
import type { PromptExtras } from "#src/session/prompts";
|
|
16
|
-
import type { PreloadedSkill } from "#src/session/skill-loader";
|
|
17
15
|
import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types";
|
|
18
16
|
|
|
19
17
|
// ── Public interfaces ────────────────────────────────────────────────────────
|
|
@@ -21,19 +19,17 @@ import type { AgentPromptConfig, SubagentType, ThinkingLevel } from "#src/types"
|
|
|
21
19
|
/**
|
|
22
20
|
* IO collaborators injected into `assembleSessionConfig`.
|
|
23
21
|
*
|
|
24
|
-
* Bundling the
|
|
22
|
+
* Bundling the IO-touching (or promptly testable) function into a single
|
|
25
23
|
* interface keeps the assembler free of direct module imports and makes it
|
|
26
24
|
* trivially testable without `vi.mock()` — callers inject real implementations
|
|
27
|
-
* at the edge (`
|
|
25
|
+
* at the edge (`create-subagent-session.ts`) or stubs in tests.
|
|
28
26
|
*/
|
|
29
27
|
export interface AssemblerIO {
|
|
30
|
-
preloadSkills: (skills: string[], cwd: string) => PreloadedSkill[];
|
|
31
28
|
buildAgentPrompt: (
|
|
32
29
|
config: AgentPromptConfig,
|
|
33
30
|
cwd: string,
|
|
34
31
|
env: EnvInfo,
|
|
35
32
|
parentPrompt?: string,
|
|
36
|
-
extras?: PromptExtras,
|
|
37
33
|
) => string;
|
|
38
34
|
}
|
|
39
35
|
|
|
@@ -61,14 +57,12 @@ export interface AssemblerContext {
|
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
/**
|
|
64
|
-
* Narrow slice of
|
|
60
|
+
* Narrow slice of per-spawn execution fields consumed by the assembler.
|
|
65
61
|
* All fields are optional — callers pass only what they have.
|
|
66
62
|
*/
|
|
67
63
|
export interface AssemblerOptions {
|
|
68
64
|
/** Override working directory (e.g. for worktree isolation). */
|
|
69
65
|
cwd?: string;
|
|
70
|
-
/** When true, forces extensions and skills to false. */
|
|
71
|
-
isolated?: boolean;
|
|
72
66
|
/** Explicit model override — wins over agentConfig.model and parent model. */
|
|
73
67
|
model?: unknown;
|
|
74
68
|
/** Explicit thinking level — wins over agentConfig.thinking. */
|
|
@@ -76,7 +70,7 @@ export interface AssemblerOptions {
|
|
|
76
70
|
}
|
|
77
71
|
|
|
78
72
|
/**
|
|
79
|
-
* Assembled configuration returned to `
|
|
73
|
+
* Assembled configuration returned to `createSubagentSession()`.
|
|
80
74
|
* Contains everything needed to create the SDK session and filter tools —
|
|
81
75
|
* with no SDK object references.
|
|
82
76
|
*/
|
|
@@ -87,8 +81,6 @@ export interface SessionConfig {
|
|
|
87
81
|
systemPrompt: string;
|
|
88
82
|
/** Built-in tool name allowlist for this agent type. */
|
|
89
83
|
toolNames: string[];
|
|
90
|
-
/** Resolved extensions setting: true = inherit all, false = none. */
|
|
91
|
-
extensions: boolean;
|
|
92
84
|
/**
|
|
93
85
|
* Resolved model instance (undefined → use parent model as passed to SDK).
|
|
94
86
|
* Opaque handle — the assembler passes it through without inspection.
|
|
@@ -97,10 +89,6 @@ export interface SessionConfig {
|
|
|
97
89
|
model: unknown;
|
|
98
90
|
/** Resolved thinking level (undefined → inherit from session). */
|
|
99
91
|
thinkingLevel: ThinkingLevel | undefined;
|
|
100
|
-
/** Whether to skip skill loading in the resource loader (`noSkills` flag). */
|
|
101
|
-
noSkills: boolean;
|
|
102
|
-
/** Prompt extras (memory block, preloaded skill blocks) — for transparency. */
|
|
103
|
-
extras: PromptExtras;
|
|
104
92
|
/** Per-agent configured max turns (from agentConfig.maxTurns). */
|
|
105
93
|
agentMaxTurns: number | undefined;
|
|
106
94
|
}
|
|
@@ -150,7 +138,7 @@ function resolveDefaultModel(
|
|
|
150
138
|
*
|
|
151
139
|
* @param type The subagent type name (case-insensitive registry lookup).
|
|
152
140
|
* @param ctx Narrow context from the parent session.
|
|
153
|
-
* @param options Per-call overrides (cwd,
|
|
141
|
+
* @param options Per-call overrides (cwd, model, thinkingLevel).
|
|
154
142
|
* @param env Pre-resolved environment info from `detectEnv()`.
|
|
155
143
|
* @param registry Agent config lookup — provides resolveAgentConfig and getToolNamesForType.
|
|
156
144
|
* @param io IO collaborators (skill loader, memory builder, prompt builder).
|
|
@@ -167,21 +155,6 @@ export function assembleSessionConfig(
|
|
|
167
155
|
|
|
168
156
|
const effectiveCwd = options.cwd ?? ctx.cwd;
|
|
169
157
|
|
|
170
|
-
// Resolve extensions/skills: isolated overrides to false
|
|
171
|
-
const extensions = options.isolated ? false : agentConfig.extensions;
|
|
172
|
-
const skills = options.isolated ? false : agentConfig.skills;
|
|
173
|
-
|
|
174
|
-
// Build prompt extras (memory, preloaded skills)
|
|
175
|
-
const extras: PromptExtras = {};
|
|
176
|
-
|
|
177
|
-
// Skill preloading: when skills is string[], preload their content into the prompt
|
|
178
|
-
if (Array.isArray(skills)) {
|
|
179
|
-
const loaded = io.preloadSkills(skills, effectiveCwd);
|
|
180
|
-
if (loaded.length > 0) {
|
|
181
|
-
extras.skillBlocks = loaded;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
158
|
const toolNames = registry.getToolNamesForType(type);
|
|
186
159
|
|
|
187
160
|
// Build system prompt from the resolved agent config
|
|
@@ -190,13 +163,8 @@ export function assembleSessionConfig(
|
|
|
190
163
|
effectiveCwd,
|
|
191
164
|
env,
|
|
192
165
|
ctx.parentSystemPrompt,
|
|
193
|
-
extras,
|
|
194
166
|
);
|
|
195
167
|
|
|
196
|
-
// noSkills: when we've already preloaded skills into the prompt, or skills = false,
|
|
197
|
-
// tell the resource loader not to load them again.
|
|
198
|
-
const noSkills = skills === false || Array.isArray(skills);
|
|
199
|
-
|
|
200
168
|
// Model resolution: explicit option > config model string > parent model
|
|
201
169
|
const model =
|
|
202
170
|
options.model ??
|
|
@@ -205,18 +173,15 @@ export function assembleSessionConfig(
|
|
|
205
173
|
// Thinking level: explicit option > agent config > undefined (inherit)
|
|
206
174
|
const thinkingLevel = options.thinkingLevel ?? agentConfig.thinking;
|
|
207
175
|
|
|
208
|
-
// Per-agent max turns (combined with
|
|
176
|
+
// Per-agent max turns (combined with per-call maxTurns and defaultMaxTurns by SubagentSession.runTurnLoop)
|
|
209
177
|
const agentMaxTurns = agentConfig.maxTurns;
|
|
210
178
|
|
|
211
179
|
return {
|
|
212
180
|
effectiveCwd,
|
|
213
181
|
systemPrompt,
|
|
214
182
|
toolNames,
|
|
215
|
-
extensions,
|
|
216
183
|
model,
|
|
217
184
|
thinkingLevel,
|
|
218
|
-
noSkills,
|
|
219
|
-
extras,
|
|
220
185
|
agentMaxTurns,
|
|
221
186
|
};
|
|
222
187
|
}
|
package/src/settings.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface SubagentsSettings {
|
|
|
8
8
|
maxConcurrent?: number;
|
|
9
9
|
/**
|
|
10
10
|
* 0 = unlimited — the extension's single source of truth for that convention:
|
|
11
|
-
* `normalizeMaxTurns()` in
|
|
11
|
+
* `normalizeMaxTurns()` in turn-limits.ts treats 0 → `undefined`, and the
|
|
12
12
|
* `/agents` → Settings input prompt explicitly says "0 = unlimited".
|
|
13
13
|
*/
|
|
14
14
|
defaultMaxTurns?: number;
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -220,11 +220,6 @@ Guidelines:
|
|
|
220
220
|
description: "Optional agent ID to resume from. Continues from previous context.",
|
|
221
221
|
}),
|
|
222
222
|
),
|
|
223
|
-
isolated: Type.Optional(
|
|
224
|
-
Type.Boolean({
|
|
225
|
-
description: "If true, agent gets no extension/MCP tools — only built-in tools.",
|
|
226
|
-
}),
|
|
227
|
-
),
|
|
228
223
|
inherit_context: Type.Optional(
|
|
229
224
|
Type.Boolean({
|
|
230
225
|
description:
|
|
@@ -48,7 +48,6 @@ export function spawnBackground(
|
|
|
48
48
|
description: execution.description,
|
|
49
49
|
model: execution.model,
|
|
50
50
|
maxTurns: execution.effectiveMaxTurns,
|
|
51
|
-
isolated: execution.isolated,
|
|
52
51
|
inheritContext: execution.inheritContext,
|
|
53
52
|
thinkingLevel: execution.thinking,
|
|
54
53
|
isBackground: true,
|
|
@@ -102,7 +102,6 @@ export async function runForeground(
|
|
|
102
102
|
description: execution.description,
|
|
103
103
|
model: execution.model,
|
|
104
104
|
maxTurns: execution.effectiveMaxTurns,
|
|
105
|
-
isolated: execution.isolated,
|
|
106
105
|
inheritContext: execution.inheritContext,
|
|
107
106
|
thinkingLevel: execution.thinking,
|
|
108
107
|
invocation: execution.agentInvocation,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
3
|
import type { AgentConfigLookup } from "#src/config/agent-types";
|
|
4
|
-
import { getAgentConversation } from "#src/lifecycle/agent-runner";
|
|
5
4
|
import { getSessionContextPercent } from "#src/lifecycle/usage";
|
|
5
|
+
import { getAgentConversation } from "#src/session/conversation";
|
|
6
6
|
import { formatLifetimeTokens, textResult } from "#src/tools/helpers";
|
|
7
7
|
import type { Agent } from "#src/types";
|
|
8
8
|
import { formatDuration, getDisplayName } from "#src/ui/display";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import type { AgentTypeRegistry } from "#src/config/agent-types";
|
|
12
12
|
import { resolveAgentInvocationConfig } from "#src/config/invocation-config";
|
|
13
|
-
import { normalizeMaxTurns } from "#src/lifecycle/
|
|
13
|
+
import { normalizeMaxTurns } from "#src/lifecycle/turn-limits";
|
|
14
14
|
import { resolveInvocationModel } from "#src/session/model-resolver";
|
|
15
15
|
import type { AgentInvocation, SubagentType, ThinkingLevel } from "#src/types";
|
|
16
16
|
import {
|
|
@@ -43,7 +43,6 @@ export interface SpawnExecution {
|
|
|
43
43
|
thinking: ThinkingLevel | undefined;
|
|
44
44
|
inheritContext: boolean;
|
|
45
45
|
runInBackground: boolean;
|
|
46
|
-
isolated: boolean;
|
|
47
46
|
agentInvocation: AgentInvocation;
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -102,7 +101,6 @@ export function resolveSpawnConfig(
|
|
|
102
101
|
const thinking = resolvedConfig.thinking;
|
|
103
102
|
const inheritContext = resolvedConfig.inheritContext;
|
|
104
103
|
const runInBackground = resolvedConfig.runInBackground;
|
|
105
|
-
const isolated = resolvedConfig.isolated;
|
|
106
104
|
|
|
107
105
|
// Compute display model name (only shown when different from parent)
|
|
108
106
|
const parentModelId = modelInfo.parentModel?.id;
|
|
@@ -120,7 +118,6 @@ export function resolveSpawnConfig(
|
|
|
120
118
|
modelName,
|
|
121
119
|
thinking,
|
|
122
120
|
maxTurns: normalizeMaxTurns(resolvedConfig.maxTurns),
|
|
123
|
-
isolated,
|
|
124
121
|
inheritContext,
|
|
125
122
|
runInBackground,
|
|
126
123
|
};
|
|
@@ -147,7 +144,6 @@ export function resolveSpawnConfig(
|
|
|
147
144
|
thinking,
|
|
148
145
|
inheritContext,
|
|
149
146
|
runInBackground,
|
|
150
|
-
isolated,
|
|
151
147
|
agentInvocation,
|
|
152
148
|
},
|
|
153
149
|
presentation: { modelName, agentTags, detailBase },
|
package/src/types.ts
CHANGED
|
@@ -39,10 +39,6 @@ export interface AgentPromptConfig {
|
|
|
39
39
|
/** Unified agent configuration — used for both default and user-defined agents. */
|
|
40
40
|
export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
41
41
|
builtinToolNames?: string[];
|
|
42
|
-
/** true = inherit all extensions, false = none */
|
|
43
|
-
extensions: boolean;
|
|
44
|
-
/** true = inherit all, string[] = only listed, false = none */
|
|
45
|
-
skills: true | string[] | false;
|
|
46
42
|
model?: string;
|
|
47
43
|
thinking?: ThinkingLevel;
|
|
48
44
|
maxTurns?: number;
|
|
@@ -50,8 +46,6 @@ export interface AgentConfig extends AgentIdentity, AgentPromptConfig {
|
|
|
50
46
|
inheritContext?: boolean;
|
|
51
47
|
/** Default for spawn: run in background. undefined = caller decides. */
|
|
52
48
|
runInBackground?: boolean;
|
|
53
|
-
/** Default for spawn: no extension tools. undefined = caller decides. */
|
|
54
|
-
isolated?: boolean;
|
|
55
49
|
/** true = this is an embedded default agent (informational) */
|
|
56
50
|
isDefault?: boolean;
|
|
57
51
|
/** false = agent is hidden from the registry */
|
|
@@ -65,7 +59,6 @@ export interface AgentInvocation {
|
|
|
65
59
|
modelName?: string;
|
|
66
60
|
thinking?: ThinkingLevel;
|
|
67
61
|
maxTurns?: number;
|
|
68
|
-
isolated?: boolean;
|
|
69
62
|
inheritContext?: boolean;
|
|
70
63
|
runInBackground?: boolean;
|
|
71
64
|
}
|
|
@@ -47,13 +47,8 @@ export function buildEjectContent(cfg: AgentConfig): string {
|
|
|
47
47
|
if (cfg.thinking) fmFields.push(`thinking: ${cfg.thinking}`);
|
|
48
48
|
if (cfg.maxTurns) fmFields.push(`max_turns: ${cfg.maxTurns}`);
|
|
49
49
|
fmFields.push(`prompt_mode: ${cfg.promptMode}`);
|
|
50
|
-
if (!cfg.extensions) fmFields.push("extensions: false");
|
|
51
|
-
if (cfg.skills === false) fmFields.push("skills: false");
|
|
52
|
-
else if (Array.isArray(cfg.skills))
|
|
53
|
-
fmFields.push(`skills: ${cfg.skills.join(", ")}`);
|
|
54
50
|
if (cfg.inheritContext) fmFields.push("inherit_context: true");
|
|
55
51
|
if (cfg.runInBackground) fmFields.push("run_in_background: true");
|
|
56
|
-
if (cfg.isolated) fmFields.push("isolated: true");
|
|
57
52
|
return `---\n${fmFields.join("\n")}\n---\n\n${cfg.systemPrompt}\n`;
|
|
58
53
|
}
|
|
59
54
|
|
|
@@ -104,11 +104,8 @@ model: <optional model as "provider/modelId", e.g. "anthropic/claude-haiku-4-5-2
|
|
|
104
104
|
thinking: <optional thinking level: off, minimal, low, medium, high, xhigh. Omit to inherit>
|
|
105
105
|
max_turns: <optional max agentic turns. 0 or omit for unlimited (default)>
|
|
106
106
|
prompt_mode: <"replace" (body IS the full system prompt) or "append" (body is appended to default prompt). Default: replace>
|
|
107
|
-
extensions: <true (inherit all MCP/extension tools) or false (none). Default: true>
|
|
108
|
-
skills: <true (inherit all), false (none), or comma-separated skill names to preload into prompt. Default: true>
|
|
109
107
|
inherit_context: <true to fork parent conversation into agent so it sees chat history. Default: false>
|
|
110
108
|
run_in_background: <true to run in background by default. Default: false>
|
|
111
|
-
isolated: <true for no extension/MCP tools, only built-in tools. Default: false>
|
|
112
109
|
---
|
|
113
110
|
|
|
114
111
|
<system prompt body — instructions for the agent>
|
|
@@ -120,7 +117,6 @@ Guidelines for choosing settings:
|
|
|
120
117
|
- Use prompt_mode: append if the agent should keep the default system prompt and add specialization on top
|
|
121
118
|
- Use prompt_mode: replace for fully custom agents with their own personality/instructions
|
|
122
119
|
- Set inherit_context: true if the agent needs to know what was discussed in the parent conversation
|
|
123
|
-
- Set isolated: true if the agent should NOT have access to MCP servers or other extensions
|
|
124
120
|
- Only include frontmatter fields that differ from defaults — omit fields where the default is fine
|
|
125
121
|
|
|
126
122
|
Write the file using the write tool. Only write the file, nothing else.`;
|
package/src/ui/display.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface AgentDetails {
|
|
|
30
30
|
spinnerFrame?: number;
|
|
31
31
|
/** Short model name if different from parent (e.g. "haiku", "sonnet"). */
|
|
32
32
|
modelName?: string;
|
|
33
|
-
/** Notable config tags (e.g. ["thinking: high", "
|
|
33
|
+
/** Notable config tags (e.g. ["thinking: high", "inherit context"]). */
|
|
34
34
|
tags?: string[];
|
|
35
35
|
/** Current turn count. */
|
|
36
36
|
turnCount?: number;
|
|
@@ -135,7 +135,6 @@ export function buildInvocationTags(
|
|
|
135
135
|
const tags: string[] = [];
|
|
136
136
|
if (!invocation) return { tags };
|
|
137
137
|
if (invocation.thinking) tags.push(`thinking: ${invocation.thinking}`);
|
|
138
|
-
if (invocation.isolated) tags.push("isolated");
|
|
139
138
|
if (invocation.inheritContext) tags.push("inherit context");
|
|
140
139
|
if (invocation.runInBackground) tags.push("background");
|
|
141
140
|
if (invocation.maxTurns != null) tags.push(`max turns: ${invocation.maxTurns}`);
|