@hcflabs/pi-subagent 28.3.0 → 28.3.1

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/README.md CHANGED
@@ -37,7 +37,7 @@ Commands, prompts, and UI are for you. Tools and the packaged skill are for Main
37
37
  | `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. With `dryRun: true`, check the request and show the resolved plan without a launch. |
38
38
  | `subagent_repos` | tool | List the child Git repositories below the Pi working directory and the `repo` selector of each. Read-only. |
39
39
  | `subagent_status` | tool | Read saved isolated state, exact identities, failed review verdicts, and allowed continuation. Does not replay work. Supports structured output for `codemode`. |
40
- | `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation, not staging or integration. Same-worker correction retries accept optional `instruction` text. |
40
+ | `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation, not staging or integration. Same-worker correction retries accept optional `instruction` text. `verify` can also adopt a later clean committed candidate from the same settled worker. That candidate must pass its normal checks and judgment. |
41
41
  | `subagent_stage` | tool | Stage, resolve, reject, or revise an exact candidate. Rejecting a staged candidate freezes its generation. |
42
42
  | `subagent_integrate` | tool | Advance dependents, refresh, validate, correct, promote, reconcile, clean up promotion, or release rejected and superseded resources. |
43
43
  | `subagent_abort` | tool | Terminate an isolated request without discarding retained work. Retained candidates and integration work require release or proof of absence. |
@@ -104,6 +104,20 @@ export declare class HerdrHostRuntime implements HostRuntime {
104
104
  outcome: "unknown";
105
105
  failure: string;
106
106
  }>;
107
+ /**
108
+ * Proves that the exact owned worker is settled with a `stop` Final turn on its latest recorded prompt.
109
+ * The method reads the lifecycle and the session one time. It sends no prompt and closes nothing.
110
+ */
111
+ inspectSettledWorker(input: {
112
+ task: TaskRequest;
113
+ attempt: TaskAttempt;
114
+ workerId: string;
115
+ }, context: OperationContext): Promise<{
116
+ outcome: "settled";
117
+ } | {
118
+ outcome: "active" | "unknown";
119
+ failure: string;
120
+ }>;
107
121
  private proveAbsentAgentForCleanup;
108
122
  cleanupHost(input: {
109
123
  requestId: ExecuteRequest["id"];
@@ -23,6 +23,8 @@ const STALLED_PROMPT_POLL_MS = 250;
23
23
  const DEFAULT_SETTLED_IDLE_MS = 600_000;
24
24
  const HOST_LAYOUT_POLL_MS = 50;
25
25
  const HOST_LAYOUT_MAX_POLLS = 40;
26
+ const SHELL_IDLE_POLL_MS = 100;
27
+ const SHELL_IDLE_MAX_POLLS = 100;
26
28
  const DIAGNOSTIC_LIMIT = 8 * 1024;
27
29
  const LEASE_MODE = 0o600;
28
30
  const DIRECTORY_MODE = 0o700;
@@ -663,6 +665,41 @@ export class HerdrHostRuntime {
663
665
  return { outcome: "unknown", failure: safeText(error) };
664
666
  }
665
667
  }
668
+ /**
669
+ * Proves that the exact owned worker is settled with a `stop` Final turn on its latest recorded prompt.
670
+ * The method reads the lifecycle and the session one time. It sends no prompt and closes nothing.
671
+ */
672
+ async inspectSettledWorker(input, context) {
673
+ try {
674
+ const allocation = ownedIntent(input.attempt, "agent");
675
+ requireIntentIdentity(allocation, input.attempt);
676
+ assertAgentIntent(allocation, input.attempt);
677
+ if (input.workerId !== allocation.agentName)
678
+ throw new Error("Settled worker inspection does not target the exact saved agent.");
679
+ this.assertLeasePath(allocation.leasePath, allocation.token);
680
+ await assertPrivateLease(allocation.leasePath, false);
681
+ // runWorker adds the prompt count after it records the prompt. So the latest prompt has the identity of the count.
682
+ const index = input.attempt.prompts.length;
683
+ if (!index)
684
+ throw new Error("Settled worker inspection requires a recorded worker prompt.");
685
+ const lifecycle = await this.waitForAgentLifecycle(allocation, context, true);
686
+ if (!SETTLED_AGENT_STATES.has(lifecycle.status)) {
687
+ return { outcome: "active", failure: `The exact worker is not settled: ${lifecycle.status}.` };
688
+ }
689
+ const suffix = `\n\nTurn identity: ${input.attempt.correlationToken}:${index}`;
690
+ const turn = await this.workerSessionTurn(allocation, (text) => text.endsWith(suffix), context.signal);
691
+ if (turn.state === "succeeded")
692
+ return { outcome: "settled" };
693
+ if (turn.state === "failed")
694
+ return { outcome: "unknown", failure: `The latest worker prompt has no stop Final turn. ${turn.failure}` };
695
+ return { outcome: "active", failure: turn.state === "running"
696
+ ? "The latest worker prompt has no Final turn yet."
697
+ : "The worker session has no entry for the latest worker prompt." };
698
+ }
699
+ catch (error) {
700
+ return { outcome: "unknown", failure: safeText(error) };
701
+ }
702
+ }
666
703
  async proveAbsentAgentForCleanup(allocation, attempt, context) {
667
704
  requireIntentIdentity(allocation, attempt);
668
705
  assertAgentIntent(allocation, attempt);
@@ -851,7 +888,7 @@ export class HerdrHostRuntime {
851
888
  if ((await this.scanLease(allocation.leasePath, allocation.worktreeCwd, context)).length) {
852
889
  throw new Error("Agent start requires an empty exact process lease.");
853
890
  }
854
- await this.assertStartableAgentPane(allocation, context, { requireExclusiveTty: false });
891
+ await this.assertStartableAgentPane(allocation, context, { requireExclusiveTty: false, awaitIdleShell: true });
855
892
  const options = this.processOptions(allocation.worktreeCwd, context, HERDR_OPERATION_CAP_MS);
856
893
  const handle = await acquireLaunch();
857
894
  return await withTransientLaunch(handle, async (launch) => {
@@ -1055,24 +1092,41 @@ export class HerdrHostRuntime {
1055
1092
  || (pane.agent !== undefined && pane.agent !== null) || pane.agent_status !== "unknown") {
1056
1093
  throw new Error("The exact saved agent pane is not empty and startable in its owned worktree.");
1057
1094
  }
1058
- const processResponse = await this.herdr.json(["pane", "process-info", "--pane", allocation.paneId], this.processOptions(allocation.worktreeCwd, context, HERDR_OPERATION_CAP_MS));
1059
- const processResult = resultRecord(processResponse, "Herdr agent pane process-info response");
1060
- if (processResult.type !== "pane_process_info")
1061
- throw new Error("Herdr agent pane process-info response has the wrong type.");
1062
- const processInfo = record(processResult.process_info, "Herdr agent pane process-info");
1063
- const shellPid = processInfo.shell_pid;
1064
- const foreground = processInfo.foreground_processes;
1065
- if (exactString(processInfo.pane_id, "Herdr agent process pane ID") !== allocation.paneId
1066
- || !Number.isSafeInteger(shellPid) || Number(shellPid) <= 0
1067
- || processInfo.foreground_process_group_id !== shellPid
1068
- || !Array.isArray(foreground) || foreground.length !== 1) {
1069
- throw new Error("The exact saved agent pane process state is not an idle foreground shell.");
1070
- }
1071
- const shell = record(foreground[0], "Herdr agent pane foreground process");
1072
- if (shell.pid !== shellPid || shell.cwd !== allocation.worktreeCwd) {
1073
- throw new Error("The exact saved agent pane foreground process is not its owned idle shell.");
1074
- }
1075
- exactString(shell.name, "Herdr agent pane shell name");
1095
+ let shellPid;
1096
+ for (let poll = 0;; poll += 1) {
1097
+ const processResponse = await this.herdr.json(["pane", "process-info", "--pane", allocation.paneId], this.processOptions(allocation.worktreeCwd, context, HERDR_OPERATION_CAP_MS));
1098
+ const processResult = resultRecord(processResponse, "Herdr agent pane process-info response");
1099
+ if (processResult.type !== "pane_process_info")
1100
+ throw new Error("Herdr agent pane process-info response has the wrong type.");
1101
+ const processInfo = record(processResult.process_info, "Herdr agent pane process-info");
1102
+ if (exactString(processInfo.pane_id, "Herdr agent process pane ID") !== allocation.paneId) {
1103
+ throw new Error("The exact saved agent pane process state is not an idle foreground shell.");
1104
+ }
1105
+ shellPid = processInfo.shell_pid;
1106
+ const foreground = processInfo.foreground_processes;
1107
+ const idleGroup = Number.isSafeInteger(shellPid) && Number(shellPid) > 0
1108
+ && processInfo.foreground_process_group_id === shellPid
1109
+ && Array.isArray(foreground) && foreground.length === 1;
1110
+ const shell = idleGroup ? record(foreground[0], "Herdr agent pane foreground process") : undefined;
1111
+ if (shell && shell.pid === shellPid && shell.cwd === allocation.worktreeCwd) {
1112
+ exactString(shell.name, "Herdr agent pane shell name");
1113
+ break;
1114
+ }
1115
+ if (!options.awaitIdleShell) {
1116
+ if (!idleGroup)
1117
+ throw new Error("The exact saved agent pane process state is not an idle foreground shell.");
1118
+ throw new Error("The exact saved agent pane foreground process is not its owned idle shell.");
1119
+ }
1120
+ if (poll + 1 >= SHELL_IDLE_MAX_POLLS) {
1121
+ const processes = Array.isArray(foreground)
1122
+ ? foreground.slice(0, 20).map((process) => `${process?.pid}:${process?.name}`).join(", ")
1123
+ : "malformed";
1124
+ const diagnostic = safeText(`shell_pid=${shellPid}, foreground_process_group_id=${processInfo.foreground_process_group_id}, foreground=[${processes}]`);
1125
+ throw new Error(`The exact saved agent pane process state is not an idle foreground shell. ${diagnostic}`);
1126
+ }
1127
+ // Only fresh allocation waits for shell startup. Recovery requires an immediate safety proof.
1128
+ await this.delay(SHELL_IDLE_POLL_MS, context.signal);
1129
+ }
1076
1130
  if (!options.requireExclusiveTty)
1077
1131
  return;
1078
1132
  // Herdr's foreground list excludes background jobs, so recovery inventories every process on the shell's controlling TTY.
package/dist/index.js CHANGED
@@ -12,8 +12,6 @@ export { loadRoleMcpConfig, parseRoleMcpAllowlist } from "./mcp-role.js";
12
12
  export { addUsage, capEphemeralSubagentOutput, createEphemeralSubagentExecutor, DEFAULT_MAX_TURNS, EphemeralSubagentError, EXECUTION_BUDGET_ENV, formatDuration, } from "./ephemeral.js";
13
13
  export { createChildWorktree, finalizeChildWorktree, inspectIndexFlags, inspectWorktreeDirty, WorktreeSetupError, } from "./worktree.js";
14
14
  export { prepareExactReviewEvidence, REVIEW_MAX_PATCH_BYTES, REVIEW_MAX_PATHS, } from "./review-evidence.js";
15
- const CODEX_ALIAS = /^openai-codex-(?:[2-9]|[1-9]\d+)$/;
16
- const MULTI_CODEX_EXTENSION = fileURLToPath(import.meta.resolve("@hcflabs/pi-multi-codex/extensions/multi-codex.ts"));
17
15
  const ROLE_MCP_EXTENSION = fileURLToPath(new URL("../extensions/role-mcp.ts", import.meta.url));
18
16
  const ROLE_TOOLS_EXTENSION = fileURLToPath(new URL("../extensions/role-tools.ts", import.meta.url));
19
17
  const GIT_READ_EXTENSION = fileURLToPath(new URL("../extensions/git-read.ts", import.meta.url));
@@ -510,7 +508,6 @@ function prepareRoleLaunchFromSkills(ctx, input, skills) {
510
508
  }
511
509
  const extensions = [
512
510
  ...selectedExtensions,
513
- ...(CODEX_ALIAS.test(input.route.model.provider) ? [MULTI_CODEX_EXTENSION] : []),
514
511
  // Always loaded: it connects the servers that Role extensions register; `mcps` gates only mcp.json.
515
512
  ROLE_MCP_EXTENSION,
516
513
  ...(role.tools.includes("git_read") ? [GIT_READ_EXTENSION] : []),
package/dist/runner.d.ts CHANGED
@@ -193,6 +193,20 @@ export interface HostRuntime {
193
193
  outcome: "unknown";
194
194
  failure: string;
195
195
  }>;
196
+ /**
197
+ * Optional. Proves that the exact owned worker is settled with a `stop` Final turn on its latest recorded prompt.
198
+ * Without this method, `verify` cannot adopt a later candidate.
199
+ */
200
+ inspectSettledWorker?(input: {
201
+ task: TaskRequest;
202
+ attempt: TaskAttempt;
203
+ workerId: string;
204
+ }, context: OperationContext): Promise<{
205
+ outcome: "settled";
206
+ } | {
207
+ outcome: "active" | "unknown";
208
+ failure: string;
209
+ }>;
196
210
  cleanupHost(input: {
197
211
  requestId: ExecuteRequest["id"];
198
212
  kind: HostCleanupKind;
@@ -437,6 +451,13 @@ export declare class IsolatedRunner {
437
451
  private runCheckBatch;
438
452
  private runReview;
439
453
  private retainCandidate;
454
+ private lateAdoptionAvailable;
455
+ /**
456
+ * Adopts the later clean committed candidate of the same settled worker. It sends no prompt and uses no correction.
457
+ * A refusal before the candidate is saved throws and changes no state.
458
+ * Returns a response when the adopted candidate needs attention. Returns undefined when readiness can continue.
459
+ */
460
+ private adoptLateCandidate;
440
461
  private verifyRetainedTask;
441
462
  private retry;
442
463
  private finalize;
package/dist/runner.js CHANGED
@@ -412,6 +412,22 @@ function correctionEligible(request, attempt) {
412
412
  && sameIdentity(attempt.preliminaryReview.tip, attempt.candidate)
413
413
  && sameIdentity(attempt.preliminaryReview.identityAfter, attempt.candidate)));
414
414
  }
415
+ /**
416
+ * True when `verify` can adopt a later candidate of the same settled worker.
417
+ * The latest prompt settled without a candidate, and no candidate exists in the prompt lineage.
418
+ * The worker can still leave a candidate after a false finished reading.
419
+ */
420
+ function lateAdoptionEligible(task, attempt) {
421
+ const last = attempt.prompts.at(-1);
422
+ return task.status === "needs_attention"
423
+ && !attempt.superseded
424
+ && !attempt.candidate && !attempt.candidateBase
425
+ && last?.status === "settled" && !last.candidate
426
+ && !attempt.prompts.some((prompt) => prompt.status === "ambiguous" || prompt.status === "submitting")
427
+ && !attempt.termination && !attempt.readiness && !attempt.preliminaryChecks && !attempt.preliminaryReview
428
+ && Boolean(allocationByKind(attempt, "agent")?.agentName && allocationByKind(attempt, "worktree")?.worktree)
429
+ && last.preCandidate.head === attempt.waveBase.head;
430
+ }
415
431
  function terminal(state) {
416
432
  return TERMINAL_RUN_STATUSES.some((status) => status === state.status);
417
433
  }
@@ -2925,11 +2941,83 @@ export class IsolatedRunner {
2925
2941
  state.integration.candidates.sort((a, b) => state.tasks.findIndex((item) => item.taskId === a.taskId)
2926
2942
  - state.tasks.findIndex((item) => item.taskId === b.taskId));
2927
2943
  }
2944
+ lateAdoptionAvailable(state, task, attempt) {
2945
+ return Boolean(this.hostRuntime.inspectSettledWorker) && !state.integration.candidates.length
2946
+ && lateAdoptionEligible(task, attempt);
2947
+ }
2948
+ /**
2949
+ * Adopts the later clean committed candidate of the same settled worker. It sends no prompt and uses no correction.
2950
+ * A refusal before the candidate is saved throws and changes no state.
2951
+ * Returns a response when the adopted candidate needs attention. Returns undefined when readiness can continue.
2952
+ */
2953
+ async adoptLateCandidate(handle, task, attempt, scope) {
2954
+ const state = handle.state;
2955
+ const request = changesetTaskRequest(state, task.taskId);
2956
+ const last = attempt.prompts.at(-1);
2957
+ if (last.preCandidate.head !== attempt.waveBase.head) {
2958
+ throw new Error("Late candidate adoption requires a prompt lineage that starts at the wave base.");
2959
+ }
2960
+ const workerId = allocationByKind(attempt, "agent").agentName;
2961
+ const branch = `refs/heads/${allocationByKind(attempt, "worktree").worktree.branch}`;
2962
+ const requireSettled = async () => {
2963
+ const result = await this.callProductive(handle, scope, async (context) => await this.hostRuntime.inspectSettledWorker({
2964
+ task: request, attempt, workerId,
2965
+ }, context));
2966
+ if (result.outcome !== "settled") {
2967
+ throw new Error(`Late candidate adoption requires the exact worker to be settled with a stop Final turn on its latest prompt (${result.outcome}): ${boundedRuntimeText(result.failure, "Settled worker failure")}`);
2968
+ }
2969
+ };
2970
+ await requireSettled();
2971
+ const candidate = runtimeIdentity(await this.callProductive(handle, scope, async (context) => await this.gitRuntime.inspectRetainedTask({
2972
+ root: state.root, task: request, attempt,
2973
+ }, context)), "Late task candidate identity");
2974
+ if (!isCleanCommitted(candidate) || candidate.branch !== branch || candidate.head === attempt.waveBase.head) {
2975
+ throw new Error("Late candidate adoption requires a new clean committed candidate on the owned branch.");
2976
+ }
2977
+ // A second proof after the Git read shows that the worker stayed settled on the same latest prompt.
2978
+ await requireSettled();
2979
+ last.candidate = candidate;
2980
+ attempt.candidate = candidate;
2981
+ attempt.candidateBase = attempt.waveBase;
2982
+ await this.saveProductive(handle);
2983
+ try {
2984
+ attempt.preliminaryChecks = await this.runCheckBatch(handle, request.checks, candidate, "preliminary", scope, task.taskId);
2985
+ await this.saveProductive(handle);
2986
+ if (!attempt.preliminaryChecks.passed) {
2987
+ this.attention(task, sameIdentity(attempt.preliminaryChecks.identityAfter, candidate)
2988
+ ? "A declared task check failed on the adopted candidate."
2989
+ : "A declared task check changed the adopted candidate.");
2990
+ await this.saveProductive(handle);
2991
+ return this.response(state);
2992
+ }
2993
+ if (request.judgment) {
2994
+ attempt.preliminaryReview = await this.runReview(handle, request.judgment.criterion, attempt.candidateBase, candidate, request.judgment.role, request.judgment.modelClass, "preliminary", scope, task.taskId);
2995
+ await this.saveProductive(handle);
2996
+ if (!attempt.preliminaryReview.passed) {
2997
+ this.attention(task, "Preliminary Judgment did not return exact PASS on the adopted candidate.");
2998
+ await this.saveProductive(handle);
2999
+ return this.response(state);
3000
+ }
3001
+ }
3002
+ }
3003
+ catch (error) {
3004
+ this.rethrowStopped(error);
3005
+ this.attention(task, `Adopted candidate validation was interrupted: ${errorText(error)}`);
3006
+ await this.saveProductive(handle);
3007
+ throw error;
3008
+ }
3009
+ return undefined;
3010
+ }
2928
3011
  async verifyRetainedTask(handle, task, scope) {
2929
3012
  const attempt = latestAttempt(task);
2930
3013
  if (attempt.prompts.some((prompt) => prompt.status === "ambiguous")) {
2931
3014
  throw new Error("An ambiguous delivered prompt is retained for inspection or explicit abort and is never replayed.");
2932
3015
  }
3016
+ if (!attempt.candidate && this.lateAdoptionAvailable(handle.state, task, attempt)) {
3017
+ const stopped = await this.adoptLateCandidate(handle, task, attempt, scope);
3018
+ if (stopped)
3019
+ return stopped;
3020
+ }
2933
3021
  const candidate = runtimeIdentity(await this.callProductive(handle, scope, async (context) => await this.gitRuntime.inspectRetainedTask({
2934
3022
  root: handle.state.root, task: changesetTaskRequest(handle.state, task.taskId), attempt,
2935
3023
  }, context)), "Retained task candidate identity");
@@ -3334,7 +3422,7 @@ export class IsolatedRunner {
3334
3422
  else if (attempt && this.correctionAllowed(state, changesetTaskRequest(state, attention.taskId), attempt)) {
3335
3423
  continuation = { id: state.request.id, action: "retry", taskId: attention.taskId };
3336
3424
  }
3337
- else if (hasEvidence) {
3425
+ else if (hasEvidence || (attempt && this.lateAdoptionAvailable(state, attention, attempt))) {
3338
3426
  continuation = { id: state.request.id, action: "verify", taskId: attention.taskId };
3339
3427
  }
3340
3428
  }
@@ -281,6 +281,8 @@ Every task declares `id`, `kind`, `role`, `modelClass`, `requirements`, `deliver
281
281
 
282
282
  Admission and worker-wave dispatch pin Main's exact committed branch, HEAD, and tree without inspecting or modifying its staged, unstaged, or untracked edits. Worker allocation verifies the same committed identity around worktree creation, including resumed waves; staged-dependent waves verify their clean integration snapshot. A dirty Main can therefore produce a checked retained candidate, but branch/HEAD drift stops new waves. The runner executes ready worker tasks concurrently, inspects clean committed candidates, runs preliminary task checks and optional preliminary judgment, then records ready candidates for Main. **It does not automatically integrate them.**
283
283
 
284
+ The extension waits up to about 10 seconds for the shell of a new worker pane to finish startup before it starts Pi.
285
+
284
286
  Main selects exact candidates through `subagent_stage` into an owned integration checkout in explicit order. Each stage is a committed merge of the candidate, not a mutation of Main. Conflicts retain the checkout for Main to resolve; `resolve` verifies the exact two-parent merge instead of replaying an uncertain merge. Main can explicitly run dependent tasks from an exact staged tip with `subagent_integrate advance` and stage their results afterward. Independent candidates may be staged without re-running workers.
285
287
 
286
288
  Once all selected work is staged, Main calls `subagent_integrate validate` on the exact clean combined tip. It acknowledges the durably saved validation intent and continues in the session-owned background, independent of later turn cancellation; session replacement/shutdown still cancels it. A follow-up reports completion; status remains authoritative if delivery is missed. This runs every final check, including `pnpm test`, unless [exact check reuse](#validate-and-promote) applies, and optional final judgment in the integration checkout before any Main mutation. A failed check/review can be followed by one Main-authored committed correction in that checkout, recorded with `correct`, then another `validate`.
@@ -406,6 +408,8 @@ Parsing rejects unknown properties, malformed evidence, invalid lineage, and v4
406
408
 
407
409
  Isolated workers save private exact Pi sessions under their owned process leases. A proved final answer without a changed clean commit requires attention. Missing or mismatched session proof cannot establish completion and never permits prompt replay.
408
410
 
411
+ A worker can leave a clean committed candidate after the runner records its turn as finished without one. `verify` can adopt that candidate after it proves that the same owned worker is settled with a `stop` Final turn on its latest prompt. The candidate then passes the task's normal preliminary checks and judgment before readiness. Adoption sends no prompt and uses no correction. It never replaces a saved candidate. While the same-worker correction is available, the reported continuation is `retry`.
412
+
409
413
  When a finished turn leaves tracked or untracked changes in a valid owned worktree, the runner sends one commit reminder per dispatch try. It saves the reminder before delivery. This uses neither a correction nor a dispatch try. The runner never commits, stages, stashes, or discards. A resulting commit follows normal candidate checks; remaining dirty work requires attention.
410
414
 
411
415
  `subagent_status` stays read-only and authoritative if delivery fails. Session replacement or shutdown suppresses stale follow-ups and logs the request ID and reason, not prompts or credentials. Synchronous delivery failures report without automatic replay. Pi reports asynchronous `send_message` failures through its runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hcflabs/pi-subagent",
3
- "version": "28.3.0",
3
+ "version": "28.3.1",
4
4
  "description": "Delegate bounded direct work or durable checked isolated task graphs to Pi roles.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -72,7 +72,6 @@
72
72
  "dependencies": {
73
73
  "@hcflabs/pi-config-store": "^4.0.0",
74
74
  "@hcflabs/pi-herdr": "^0.6.0",
75
- "@hcflabs/pi-multi-codex": "^3.0.0",
76
75
  "@hcflabs/pi-process": "^0.3.0",
77
76
  "@hcflabs/pi-repo": "^0.1.0",
78
77
  "@hcflabs/pi-task-models": "^9.0.0",