pi-cohort 7.6.1 → 7.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [7.6.3] - 2026-10-28
4
+
5
+ ### Added
6
+ - Every child process gets `PI_SUBAGENT_PARENT_SESSION_ID`, the pi session id of the session that launched it, so a session roster can link each child to its parent without parsing the session name. A nested child gets its immediate parent's id. The README now lists `PI_SUBAGENT_CHILD`, `PI_SUBAGENT_CHILD_AGENT`, `PI_SUBAGENT_RUN_ID`, and `PI_SUBAGENT_PARENT_SESSION_ID` as stable inputs for other extensions. (#34)
7
+
8
+ ## [7.6.2] - 2026-10-28
9
+
10
+ ### Added
11
+ - Each child session is named `<agent> <run id prefix>[/<index>] from <parent>`, where `<parent>` is the parent session's name or, when unnamed, its id. `/resume`, terminal titles, and live-session rosters now tell children apart instead of listing unnamed sessions. (#32)
12
+
3
13
  ## [7.6.1] - 2026-10-26
4
14
 
5
15
  ### Added
package/README.md CHANGED
@@ -167,6 +167,25 @@ Review and delivery workflows live in pi-gauntlet; pi-cohort ships delegation pr
167
167
 
168
168
  `context: "fork"` starts a child from a real branched session instead of a fresh one, when it needs the parent's history. Cost across the whole subtree - main loop plus every foreground/background/nested child - rolls up into one `Σ$` total in the footer; see [doc/observability.md](doc/observability.md).
169
169
 
170
+ Each child session is named `<agent> <run id prefix>[/<index>] from <parent>`, so `/resume`, terminal titles, and live-session rosters can tell children apart:
171
+
172
+ | Child | Session name |
173
+ |---|---|
174
+ | Single `scout` from a session named `billing reports` | `scout 3f9a2c1e from billing reports` |
175
+ | Third task (index 2) of the same parallel run | `scout 3f9a2c1e/2 from billing reports` |
176
+ | `scout` from an unnamed session | `scout 3f9a2c1e from <parent session id>` |
177
+
178
+ The run id prefix works as the `id` for `subagent` status, interrupt, and resume; the index is the child `index` for resume. A nested child names its parent child: `worker 7b41d0aa from scout 3f9a2c1e from billing reports`.
179
+
180
+ Every child process also gets these environment variables, stable inputs for other extensions (for example, a session roster linking each child to its parent):
181
+
182
+ | Variable | Value | Set |
183
+ |---|---|---|
184
+ | `PI_SUBAGENT_CHILD` | `1` | on every child |
185
+ | `PI_SUBAGENT_CHILD_AGENT` | the child's agent name | when the agent is known |
186
+ | `PI_SUBAGENT_RUN_ID` | the full run id; its prefix appears in the session name | when the run id is known |
187
+ | `PI_SUBAGENT_PARENT_SESSION_ID` | the pi session id of the session that launched the child | on every child; a nested child gets its immediate parent's id, never an inherited ancestor's |
188
+
170
189
  Full reference: agent/chain authoring in [doc/agents-and-chains.md](doc/agents-and-chains.md), exact command syntax in [doc/commands.md](doc/commands.md), the raw tool API in [doc/programmatic-api.md](doc/programmatic-api.md).
171
190
 
172
191
  ## Key concepts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cohort",
3
- "version": "7.6.1",
3
+ "version": "7.6.3",
4
4
  "description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -57,6 +57,10 @@ interface AsyncExecutionContext {
57
57
  parentModel?: string;
58
58
  /** Parent session thinking level, captured at dispatch */
59
59
  parentThinking?: string;
60
+ /** Parent session display name, or its id when unnamed, captured at dispatch */
61
+ parentSessionLabel?: string;
62
+ /** Parent session id, captured at dispatch */
63
+ parentSessionId?: string;
60
64
  executionBackend?: string;
61
65
  }
62
66
 
@@ -632,6 +636,8 @@ export function executeAsyncChain(
632
636
  sessionDir: path.join(sessionRoot ?? path.join(RUN_STATE_ROOT_DIR, "sessions"), `async-${id}`),
633
637
  asyncDir,
634
638
  sessionId: ctx.currentSessionId,
639
+ parentSessionLabel: ctx.parentSessionLabel,
640
+ parentSessionId: ctx.parentSessionId,
635
641
  runtime: ctx.runtime,
636
642
  forwardedFlags,
637
643
  worktreeSetupHook,
@@ -846,6 +852,8 @@ export function executeAsyncSingle(
846
852
  sessionDir: path.join(sessionRoot ?? path.join(RUN_STATE_ROOT_DIR, "sessions"), `async-${id}`),
847
853
  asyncDir,
848
854
  sessionId: ctx.currentSessionId,
855
+ parentSessionLabel: ctx.parentSessionLabel,
856
+ parentSessionId: ctx.parentSessionId,
849
857
  runtime: ctx.runtime,
850
858
  forwardedFlags,
851
859
  worktreeSetupHook,
@@ -117,6 +117,8 @@ interface SubagentRunConfig {
117
117
  sessionDir?: string;
118
118
  asyncDir: string;
119
119
  sessionId?: string | null;
120
+ parentSessionLabel?: string;
121
+ parentSessionId?: string;
120
122
  runtime: PiRuntimeIdentity;
121
123
  forwardedFlags?: string[];
122
124
  worktreeSetupHook?: string;
@@ -625,6 +627,8 @@ export interface SingleStepContext {
625
627
  outputFile: string;
626
628
  runtime: PiRuntimeIdentity;
627
629
  forwardedFlags?: string[];
630
+ parentSessionLabel?: string;
631
+ parentSessionId?: string;
628
632
  registerInterrupt?: (interrupt: (() => void) | undefined) => void;
629
633
  nestedRoute?: NestedRouteInfo;
630
634
  onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
@@ -790,6 +794,8 @@ export async function runSingleStep(
790
794
  runId: ctx.id,
791
795
  childAgentName: step.agent,
792
796
  childIndex: ctx.flatIndex,
797
+ parentSessionLabel: ctx.parentSessionLabel,
798
+ parentSessionId: ctx.parentSessionId,
793
799
  parentEventSink: ctx.nestedRoute?.eventSink,
794
800
  parentControlInbox: ctx.nestedRoute?.controlInbox,
795
801
  parentRootRunId: ctx.nestedRoute?.rootRunId,
@@ -936,6 +942,8 @@ export async function runSingleStep(
936
942
  runId: ctx.id,
937
943
  childAgentName: step.agent,
938
944
  childIndex: ctx.flatIndex,
945
+ parentSessionLabel: ctx.parentSessionLabel,
946
+ parentSessionId: ctx.parentSessionId,
939
947
  parentEventSink: ctx.nestedRoute?.eventSink,
940
948
  parentControlInbox: ctx.nestedRoute?.controlInbox,
941
949
  parentRootRunId: ctx.nestedRoute?.rootRunId,
@@ -1978,6 +1986,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1978
1986
  runtime: config.runtime,
1979
1987
  controlConfig: config.controlConfig,
1980
1988
  forwardedFlags: config.forwardedFlags,
1989
+ parentSessionLabel: config.parentSessionLabel,
1990
+ parentSessionId: config.parentSessionId,
1981
1991
  executionBackendCoordinator,
1982
1992
  executionBackendConfig,
1983
1993
  nestedRoute: config.nestedRoute,
@@ -2268,6 +2278,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2268
2278
  runtime: config.runtime,
2269
2279
  controlConfig: config.controlConfig,
2270
2280
  forwardedFlags: config.forwardedFlags,
2281
+ parentSessionLabel: config.parentSessionLabel,
2282
+ parentSessionId: config.parentSessionId,
2271
2283
  executionBackendCoordinator,
2272
2284
  executionBackendConfig,
2273
2285
  nestedRoute: config.nestedRoute,
@@ -2448,6 +2460,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2448
2460
  runtime: config.runtime,
2449
2461
  controlConfig: config.controlConfig,
2450
2462
  forwardedFlags: config.forwardedFlags,
2463
+ parentSessionLabel: config.parentSessionLabel,
2464
+ parentSessionId: config.parentSessionId,
2451
2465
  executionBackendCoordinator,
2452
2466
  executionBackendConfig,
2453
2467
  nestedRoute: config.nestedRoute,
@@ -9,6 +9,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
9
9
  import type { AgentConfig } from "../../agents/agents.ts";
10
10
  import { ChainClarifyComponent, type ChainClarifyResult, type BehaviorOverride } from "./chain-clarify.ts";
11
11
  import { parentModelFullId, toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
12
+ import { resolveSessionLabel } from "../../shared/session-identity.ts";
12
13
  import {
13
14
  resolveChainTemplates,
14
15
  createChainDir,
@@ -318,6 +319,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
318
319
  preferredModelProvider: input.ctx.model?.provider,
319
320
  parentModel: parentModelFullId(input.ctx.model),
320
321
  parentThinking: input.ctx.thinkingLevel,
322
+ parentSessionLabel: resolveSessionLabel(input.ctx.sessionManager),
323
+ parentSessionId: input.ctx.sessionManager.getSessionId(),
321
324
  skills: behavior.skills === false ? [] : behavior.skills,
322
325
  structuredOutput: structuredRuntime,
323
326
  acceptance: task.acceptance,
@@ -1161,6 +1164,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1161
1164
  preferredModelProvider: ctx.model?.provider,
1162
1165
  parentModel: parentModelFullId(ctx.model),
1163
1166
  parentThinking: ctx.thinkingLevel,
1167
+ parentSessionLabel: resolveSessionLabel(ctx.sessionManager),
1168
+ parentSessionId: ctx.sessionManager.getSessionId(),
1164
1169
  skills: behavior.skills === false ? [] : behavior.skills,
1165
1170
  structuredOutput: structuredRuntime,
1166
1171
  acceptance: seqStep.acceptance,
@@ -158,6 +158,8 @@ async function runSingleAttempt(
158
158
  runId: options.runId,
159
159
  childAgentName: agent.name,
160
160
  childIndex: options.index ?? 0,
161
+ parentSessionLabel: options.parentSessionLabel,
162
+ parentSessionId: options.parentSessionId,
161
163
  parentEventSink: options.nestedRoute?.eventSink,
162
164
  parentControlInbox: options.nestedRoute?.controlInbox,
163
165
  parentRootRunId: options.nestedRoute?.rootRunId,
@@ -92,6 +92,8 @@ export async function runExternalSingleAttempt(input: RunExternalSingleAttemptIn
92
92
  runId: options.runId,
93
93
  childAgentName: agent.name,
94
94
  childIndex: options.index ?? 0,
95
+ parentSessionLabel: options.parentSessionLabel,
96
+ parentSessionId: options.parentSessionId,
95
97
  parentEventSink: options.nestedRoute?.eventSink,
96
98
  parentControlInbox: options.nestedRoute?.controlInbox,
97
99
  parentRootRunId: options.nestedRoute?.rootRunId,
@@ -37,7 +37,7 @@ import {
37
37
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
38
38
  import { executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable } from "../background/async-execution.ts";
39
39
  import { createForkContextResolver } from "../../shared/fork-context.ts";
40
- import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
40
+ import { resolveCurrentSessionId, resolveSessionLabel } from "../../shared/session-identity.ts";
41
41
  import { formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
42
42
  import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeTopLevelOutput, resolveParallelTaskOutputPath, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
43
43
  import { blockedLine } from "../shared/completion-guard.ts";
@@ -225,6 +225,8 @@ function buildAsyncContext(ctx: ExtensionContext, deps: CallExecutorDeps, cwd: s
225
225
  currentModelProvider: ctx.model?.provider,
226
226
  parentModel: parentModelFullId(ctx.model),
227
227
  parentThinking: ctx.thinkingLevel,
228
+ parentSessionLabel: resolveSessionLabel(ctx.sessionManager),
229
+ parentSessionId: ctx.sessionManager.getSessionId(),
228
230
  executionBackend: deps.config.executionBackend,
229
231
  };
230
232
  }
@@ -1503,6 +1505,8 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1503
1505
  preferredModelProvider: input.ctx.model?.provider,
1504
1506
  parentModel: parentModelFullId(input.ctx.model),
1505
1507
  parentThinking: input.ctx.thinkingLevel,
1508
+ parentSessionLabel: resolveSessionLabel(input.ctx.sessionManager),
1509
+ parentSessionId: input.ctx.sessionManager.getSessionId(),
1506
1510
  skills: effectiveSkills === false ? [] : effectiveSkills,
1507
1511
  acceptance: task.acceptance,
1508
1512
  acceptanceContext: { mode: "parallel" },
@@ -2064,6 +2068,8 @@ async function runSinglePath(data: ExecutionContextData, deps: CallExecutorDeps)
2064
2068
  preferredModelProvider: currentProvider,
2065
2069
  parentModel: parentModelFullId(ctx.model),
2066
2070
  parentThinking: ctx.thinkingLevel,
2071
+ parentSessionLabel: resolveSessionLabel(ctx.sessionManager),
2072
+ parentSessionId: ctx.sessionManager.getSessionId(),
2067
2073
  skills: effectiveSkills,
2068
2074
  acceptance: params.acceptance,
2069
2075
  acceptanceContext: { mode: "single" },
@@ -24,6 +24,7 @@ export const SUBAGENT_PARENT_CHILD_INDEX_ENV = "PI_SUBAGENT_PARENT_CHILD_INDEX";
24
24
  export const SUBAGENT_PARENT_DEPTH_ENV = "PI_SUBAGENT_PARENT_DEPTH";
25
25
  export const SUBAGENT_PARENT_PATH_ENV = "PI_SUBAGENT_PARENT_PATH";
26
26
  export const SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV = "PI_SUBAGENT_PARENT_CAPABILITY_TOKEN";
27
+ export const SUBAGENT_PARENT_SESSION_ID_ENV = "PI_SUBAGENT_PARENT_SESSION_ID";
27
28
 
28
29
  interface BuildPiArgsInput {
29
30
  baseArgs: string[];
@@ -45,6 +46,9 @@ interface BuildPiArgsInput {
45
46
  runId?: string;
46
47
  childAgentName?: string;
47
48
  childIndex?: number;
49
+ /** Parent session display name, or its id when unnamed; names the child session. */
50
+ parentSessionLabel?: string;
51
+ parentSessionId?: string;
48
52
  parentEventSink?: string;
49
53
  parentControlInbox?: string;
50
54
  parentRootRunId?: string;
@@ -79,6 +83,13 @@ export function applyThinkingSuffix(model: string | undefined, thinking: string
79
83
  return `${model}:${thinking}`;
80
84
  }
81
85
 
86
+ function childSessionName(input: BuildPiArgsInput): string | undefined {
87
+ if (!input.childAgentName || !input.runId) return undefined;
88
+ const index = input.childIndex ? `/${input.childIndex}` : "";
89
+ const parent = input.parentSessionLabel ? ` from ${input.parentSessionLabel}` : "";
90
+ return `${input.childAgentName} ${input.runId.slice(0, 8)}${index}${parent}`;
91
+ }
92
+
82
93
  export function buildPiArgs(input: BuildPiArgsInput, fileSystem: BuildPiArgsFileSystem = fs): BuildPiArgsResult {
83
94
  const args = [...input.baseArgs];
84
95
  // Forwarded flags are already name-deduped by deriveForwardedFlags; append only
@@ -100,6 +111,11 @@ export function buildPiArgs(input: BuildPiArgsInput, fileSystem: BuildPiArgsFile
100
111
  }
101
112
  }
102
113
 
114
+ const sessionName = childSessionName(input);
115
+ if (sessionName) {
116
+ args.push("--name", sessionName);
117
+ }
118
+
103
119
  const modelArg = applyThinkingSuffix(input.model, input.thinking);
104
120
  if (modelArg) {
105
121
  args.push("--model", modelArg);
@@ -204,6 +220,8 @@ export function buildPiArgs(input: BuildPiArgsInput, fileSystem: BuildPiArgsFile
204
220
  env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] = fanoutAuthorized
205
221
  ? input.parentCapabilityToken ?? process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV] ?? ""
206
222
  : "";
223
+ // Always set, so a grandchild sees its immediate parent rather than an inherited ancestor.
224
+ env[SUBAGENT_PARENT_SESSION_ID_ENV] = input.parentSessionId ?? "";
207
225
  env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0";
208
226
  env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0";
209
227
  if (input.runId) {
@@ -3,8 +3,18 @@ interface SessionIdentityManager {
3
3
  getSessionId(): string | null | undefined;
4
4
  }
5
5
 
6
+ interface SessionLabelManager {
7
+ getSessionName(): string | undefined;
8
+ getSessionId(): string;
9
+ }
10
+
6
11
  export function resolveCurrentSessionId(sessionManager: SessionIdentityManager): string {
7
12
  const sessionId = sessionManager.getSessionFile() ?? sessionManager.getSessionId();
8
13
  if (!sessionId) throw new Error("Current session identity is unavailable.");
9
14
  return sessionId;
10
15
  }
16
+
17
+ /** The session's display name, or its id when unnamed. */
18
+ export function resolveSessionLabel(sessionManager: SessionLabelManager): string {
19
+ return sessionManager.getSessionName() || sessionManager.getSessionId();
20
+ }
@@ -830,6 +830,10 @@ export interface RunSyncOptions {
830
830
  parentModel?: string;
831
831
  /** Parent session thinking level; used when the agent sets no `thinking` */
832
832
  parentThinking?: string;
833
+ /** Parent session display name, or its id when unnamed; names the child session */
834
+ parentSessionLabel?: string;
835
+ /** Parent session id; exported to the child as PI_SUBAGENT_PARENT_SESSION_ID */
836
+ parentSessionId?: string;
833
837
  /** Skills to inject (overrides agent default if provided) */
834
838
  skills?: string[];
835
839
  structuredOutput?: {