@piagent/platform 1.6.0 → 1.6.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.
Files changed (213) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +17 -14
  3. package/README.vi.md +26 -1
  4. package/benchmarks/production-v1/spend-control.v1.json +27 -3
  5. package/benchmarks/production-v1/suite.json +5 -1
  6. package/catalog/capabilities.json +16 -16
  7. package/docs/authority-manifest.md +1 -1
  8. package/docs/auto-delegation-policy.md +38 -73
  9. package/docs/command-reference-vietnamese.md +23 -31
  10. package/docs/context-engine.md +5 -3
  11. package/docs/context-window-policy.md +37 -1
  12. package/docs/distribution-standard.md +7 -7
  13. package/docs/en/architecture.md +26 -13
  14. package/docs/languages.json +4 -4
  15. package/docs/operator-manual-vietnamese.md +21 -41
  16. package/docs/publishing-for-teams.md +5 -0
  17. package/docs/quality-benchmark.md +179 -23
  18. package/docs/release-install-policy.md +6 -6
  19. package/docs/runtime-harness-standard.md +3 -3
  20. package/docs/security-threat-model.md +1 -1
  21. package/docs/subagent-orchestration-capabilities.md +19 -51
  22. package/docs/subagents-and-multiagent.md +21 -47
  23. package/docs/task-implementation-contract.md +3 -3
  24. package/docs/task-lifecycle-vietnamese.md +1 -1
  25. package/docs/team-onboarding.md +2 -2
  26. package/docs/vi/architecture.md +26 -13
  27. package/docs-site/architecture.html +1 -1
  28. package/docs-site/benchmark.html +89 -95
  29. package/docs-site/commands.html +1 -1
  30. package/docs-site/content/benchmark.html +88 -94
  31. package/docs-site/content/en/benchmark.html +134 -90
  32. package/docs-site/content/en/index.html +19 -19
  33. package/docs-site/content/en/runtime.html +4 -4
  34. package/docs-site/content/en/whats-new.html +12 -1
  35. package/docs-site/content/en/workflows.html +2 -2
  36. package/docs-site/content/index.html +19 -19
  37. package/docs-site/content/runtime.html +4 -4
  38. package/docs-site/content/whats-new.html +12 -1
  39. package/docs-site/content/workflows.html +2 -2
  40. package/docs-site/context-engine.html +1 -1
  41. package/docs-site/en/architecture.html +1 -1
  42. package/docs-site/en/benchmark.html +135 -91
  43. package/docs-site/en/commands.html +1 -1
  44. package/docs-site/en/context-engine.html +1 -1
  45. package/docs-site/en/index.html +20 -20
  46. package/docs-site/en/mcp-approval.html +1 -1
  47. package/docs-site/en/mcp-auth.html +1 -1
  48. package/docs-site/en/mcp-servers.html +1 -1
  49. package/docs-site/en/mcp.html +1 -1
  50. package/docs-site/en/permissions.html +1 -1
  51. package/docs-site/en/profiles.html +1 -1
  52. package/docs-site/en/quickstart.html +1 -1
  53. package/docs-site/en/runtime.html +5 -5
  54. package/docs-site/en/scope.html +1 -1
  55. package/docs-site/en/security.html +1 -1
  56. package/docs-site/en/team.html +1 -1
  57. package/docs-site/en/whats-new.html +13 -2
  58. package/docs-site/en/workflows.html +3 -3
  59. package/docs-site/index.html +20 -20
  60. package/docs-site/mcp-approval.html +1 -1
  61. package/docs-site/mcp-auth.html +1 -1
  62. package/docs-site/mcp-servers.html +1 -1
  63. package/docs-site/mcp.html +1 -1
  64. package/docs-site/permissions.html +1 -1
  65. package/docs-site/profiles.html +1 -1
  66. package/docs-site/quickstart.html +1 -1
  67. package/docs-site/runtime.html +5 -5
  68. package/docs-site/scope.html +1 -1
  69. package/docs-site/security.html +1 -1
  70. package/docs-site/team.html +1 -1
  71. package/docs-site/whats-new.html +13 -2
  72. package/docs-site/workflows.html +3 -3
  73. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  74. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  75. package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
  76. package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
  77. package/evals/fixtures/helper-request.invalid.json +1 -1
  78. package/evals/fixtures/helper-request.valid.json +1 -1
  79. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
  80. package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
  81. package/evals/fs4-readiness-matrix.v1.json +2 -2
  82. package/evals/fs5-causal-arm.v1.json +2 -2
  83. package/evals/fs5-pilot-protocol.v1.json +3 -3
  84. package/evals/fs5-pilot-protocol.v2.json +8 -8
  85. package/evals/fs5-pilot-protocol.v3.json +8 -8
  86. package/evals/fs5-pilot-protocol.v4.json +6 -6
  87. package/evals/fs5-pilot-protocol.v5.json +7 -6
  88. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  89. package/evals/runtime-conformance-v1/README.md +43 -0
  90. package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
  91. package/evals/runtime-conformance-v1/lane.json +57 -0
  92. package/evals/runtime-conformance-v1/paid-canary.json +31 -0
  93. package/evals/runtime-conformance-v1/runner.mjs +456 -0
  94. package/package.json +1 -1
  95. package/packages/piagent-core/README.md +13 -4
  96. package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
  97. package/packages/piagent-core/benchmark/benchmark-core.js +4 -3
  98. package/packages/piagent-core/benchmark/benchmark-forensics.js +4 -2
  99. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  100. package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
  101. package/packages/piagent-core/benchmark/benchmark-record-validation.js +74 -5
  102. package/packages/piagent-core/benchmark/benchmark-report.js +15 -1
  103. package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
  104. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +201 -4
  105. package/packages/piagent-core/benchmark/benchmark-suite.js +22 -1
  106. package/packages/piagent-core/benchmark/benchmark-summary-support.js +4 -0
  107. package/packages/piagent-core/benchmark/benchmark-usage.js +15 -4
  108. package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
  109. package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
  110. package/packages/piagent-core/extensions/acceptance-receipt.js +89 -48
  111. package/packages/piagent-core/extensions/context-engine.js +1 -11
  112. package/packages/piagent-core/extensions/context-index-policy.js +4 -0
  113. package/packages/piagent-core/extensions/context-query-paths.js +57 -0
  114. package/packages/piagent-core/extensions/criterion-context-pack.js +16 -3
  115. package/packages/piagent-core/extensions/criterion-graph.js +4 -1
  116. package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
  117. package/packages/piagent-core/extensions/guard-types.ts +4 -0
  118. package/packages/piagent-core/extensions/piagent-guard.ts +194 -226
  119. package/packages/piagent-core/extensions/task-lifecycle.js +32 -0
  120. package/packages/piagent-core/extensions/task-state.js +69 -8
  121. package/packages/piagent-core/extensions/verification-intelligence.js +49 -12
  122. package/packages/piagent-core/extensions/workspace-evidence-roots.js +52 -16
  123. package/packages/piagent-core/package.json +1 -1
  124. package/packages/piagent-core/policies/base-policy.json +42 -2
  125. package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
  126. package/packages/piagent-core/prompts/be-to-fe.md +2 -8
  127. package/packages/piagent-core/prompts/plan.md +1 -1
  128. package/packages/piagent-core/prompts/platform-improve.md +2 -6
  129. package/packages/piagent-core/prompts/review.md +2 -7
  130. package/packages/piagent-core/prompts/scout.md +1 -1
  131. package/packages/piagent-core/prompts/task.md +4 -3
  132. package/packages/piagent-core/runtime/hooks/completion-hook.ts +7 -6
  133. package/packages/piagent-core/runtime/hooks/input-hook.ts +6 -0
  134. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +100 -1
  135. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +58 -72
  136. package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
  137. package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
  138. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
  139. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
  140. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +20 -3
  141. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +47 -11
  142. package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
  143. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
  144. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
  145. package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
  146. package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
  147. package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
  148. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +3 -0
  149. package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
  150. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +20 -10
  151. package/packages/piagent-core/runtime/quality/performance-assurance.ts +3 -3
  152. package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
  153. package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
  154. package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
  155. package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
  156. package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
  157. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +7 -13
  158. package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
  159. package/packages/piagent-core/runtime/registration/context-commands.ts +16 -2
  160. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
  161. package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
  162. package/packages/piagent-core/runtime/registration/policy-tools.ts +4 -4
  163. package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
  164. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +12 -9
  165. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
  166. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +19 -13
  167. package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
  168. package/packages/piagent-core/runtime/registration/task-start-tool.ts +57 -57
  169. package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
  170. package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
  171. package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
  172. package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
  173. package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
  174. package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
  175. package/packages/piagent-core/runtime/session/runtime-state.ts +11 -7
  176. package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
  177. package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
  178. package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
  179. package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
  180. package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
  181. package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
  182. package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
  183. package/packages/piagent-core/runtime/workflows/task-intake.ts +167 -39
  184. package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
  185. package/packages/piagent-core/subagents/piagent-planner.md +6 -4
  186. package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
  187. package/packages/piagent-core/subagents/piagent-scout.md +4 -2
  188. package/packages/piagent-core/subagents/piagent-worker.md +6 -4
  189. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CxeZqTte.js → MarkdownMessage-B4XehTCU.js} +1 -1
  190. package/packages/piagent-webui/dist/client/assets/{index-DynIMe8I.js → index-CgOEyRPG.js} +4 -4
  191. package/packages/piagent-webui/dist/client/index.html +1 -1
  192. package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
  193. package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
  194. package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
  195. package/packages/piagent-webui/gateway/gateway-session-stream.ts +37 -21
  196. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
  197. package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
  198. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +9 -2
  199. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -7
  200. package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
  201. package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
  202. package/packages/piagent-webui/server/transcript-projection.ts +91 -5
  203. package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
  204. package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
  205. package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
  206. package/schemas/project-profile.schema.json +1 -1
  207. package/schemas/task-contract.schema.json +15 -0
  208. package/scripts/benchmark-runner-core.mjs +14 -6
  209. package/scripts/benchmark-runner-finalization.mjs +4 -0
  210. package/scripts/configure-subagents.sh +42 -34
  211. package/scripts/helper-evaluation.mjs +1 -1
  212. package/scripts/team-doctor.sh +1 -1
  213. package/scripts/verify-local.sh +5 -0
@@ -195,3 +195,35 @@ export function applyRuntimeLifecycleObservation(task, observation, recordedAt =
195
195
  }
196
196
  return { changed, mode };
197
197
  }
198
+
199
+ /**
200
+ * A default assisted read-only review is a presentation checkpoint, not a
201
+ * second reasoning pass. Once runtime-owned read evidence exists and the model
202
+ * emits its final handoff, completing this one exact checkpoint preserves the
203
+ * substantive answer instead of forcing a token-heavy retry. Custom/manual
204
+ * plans and every source-change review remain explicit.
205
+ */
206
+ export function applyAssistedReadOnlyFinalHandoff(
207
+ task,
208
+ { durableReadEvidence = false, finalHandoffObserved = false } = {},
209
+ recordedAt = new Date().toISOString()
210
+ ) {
211
+ const mode = runtimeLifecycleMode(task);
212
+ if (mode !== "assisted-readonly" || !durableReadEvidence || !finalHandoffObserved) {
213
+ return { changed: false, mode };
214
+ }
215
+ const scout = task.workPlan.find((step) => step.id === "scout");
216
+ const review = task.workPlan.find((step) => step.id === "review");
217
+ if (scout?.status !== "done" || review?.status !== "in-progress") {
218
+ return { changed: false, mode };
219
+ }
220
+ return {
221
+ changed: setStep(
222
+ review,
223
+ "done",
224
+ "Runtime observed a final handoff grounded in durable read-only evidence.",
225
+ recordedAt
226
+ ),
227
+ mode
228
+ };
229
+ }
@@ -20,6 +20,7 @@ import {
20
20
  } from "./task-digest-state.js";
21
21
  export { taskDigestMigrationArchiveStatus } from "./task-digest-state.js";
22
22
  import { acceptanceReceiptWithoutWorkingTreeProof, normalizedTaskDigestFields, taskDigestContractValidationErrors } from "./task-digest-contract.js";
23
+ import { unicodeCodePointLength } from "./verification-intelligence.js";
23
24
  import {
24
25
  WORKING_TREE_DIGEST_ALGORITHM,
25
26
  isCurrentWorkingTreeDigest,
@@ -42,11 +43,16 @@ import {
42
43
  export { isGitWorkingTree } from "./workspace-evidence-roots.js";
43
44
 
44
45
  export const TASK_CONTRACT_SCHEMA_VERSION = 2; export const DEFAULT_MAX_TASK_ATTEMPTS = 3;
46
+ export const OPERATOR_REQUEST_MAX_CHARS = 8_000;
47
+ export const TASK_SUMMARY_MAX_CHARS = 2_000;
48
+ export const TASK_EXPECTED_OUTPUT_MAX_CHARS = 2_000;
49
+ export const TASK_ACCEPTANCE_CRITERION_MAX_CHARS = 1_000;
50
+ export const TASK_ACCEPTANCE_CRITERIA_MAX = 12;
45
51
  const TASK_OUTCOMES = ["pending", "completed", "blocked", "partial", "failed"], TERMINAL_TASK_OUTCOMES = new Set(["completed", "blocked", "partial", "failed"]);
46
52
  const REVIEW_LENSES = ["correctness", "tests", "scope", "security", "docs", "release", "package"];
47
53
  const TASK_CONTRACT_FIELDS = new Set([
48
54
  "schemaVersion", "taskRunId", "taskId", "sessionId", "sessionName", "changeMode", "mutationPolicy", "attempt", "maxAttempts",
49
- "previousAttempts", "summary", "riskLane", "intakeMode", "expectedOutput", "acceptanceCriteria", "criterionGraph", "scope", "outOfScope",
55
+ "previousAttempts", "summary", "operatorRequest", "operatorRequestDigest", "riskLane", "intakeMode", "expectedOutput", "acceptanceCriteria", "criterionGraph", "scope", "outOfScope",
50
56
  "protectedPaths", "requiredContext", "contextManifest", "memoryCitations", "mcpCapabilities", "verifyGroup",
51
57
  "verifyCommands", "workPlan", "reviewLenses", "orchestration", "acceptanceReceipt", "authoritySnapshot", "workingTreeDigestAlgorithm", "workingTreeDigestMigration", "baselineChangedFiles", "baselineFileDigests",
52
58
  "observedChangedFiles", "finalWorkingTreeFiles", "finalFileDigests", "changedFiles", "verifyEvidence", "trace",
@@ -136,6 +142,10 @@ function stringArray(value) {
136
142
  return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()))] : [];
137
143
  }
138
144
 
145
+ function exactCommandArray(value) {
146
+ return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string" && item.trim()))] : [];
147
+ }
148
+
139
149
  function stringRecord(value) {
140
150
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
141
151
  return Object.fromEntries(Object.entries(value)
@@ -151,6 +161,10 @@ function validTimestamp(value) {
151
161
  return typeof value === "string" && value.trim().length > 0 && Number.isFinite(Date.parse(value));
152
162
  }
153
163
 
164
+ export function operatorRequestDigest(value) {
165
+ return `operator-request-v1:${crypto.createHash("sha256").update(String(value ?? "")).digest("hex")}`;
166
+ }
167
+
154
168
  function normalizedTimestamp(value, fallback) {
155
169
  return validTimestamp(value) ? value : fallback;
156
170
  }
@@ -191,6 +205,16 @@ export function taskContractValidationErrors(input) {
191
205
  if (typeof input.taskId === "string" && safeTaskId(input.taskId) !== input.taskId) errors.push("taskId must be normalized");
192
206
  if (typeof input.summary === "string" && input.summary.trim().length < 10) errors.push("summary must be at least 10 characters");
193
207
  if (typeof input.expectedOutput === "string" && input.expectedOutput.trim().length < 10) errors.push("expectedOutput must be at least 10 characters");
208
+ const hasOperatorRequest = input.operatorRequest !== undefined;
209
+ const hasOperatorRequestDigest = input.operatorRequestDigest !== undefined;
210
+ if (hasOperatorRequest !== hasOperatorRequestDigest) errors.push("operatorRequest and operatorRequestDigest must be present together");
211
+ if (hasOperatorRequest) {
212
+ if (typeof input.operatorRequest !== "string" || !input.operatorRequest.trim()) errors.push("operatorRequest must be a non-empty string");
213
+ else {
214
+ if (unicodeCodePointLength(input.operatorRequest) > OPERATOR_REQUEST_MAX_CHARS) errors.push(`operatorRequest must contain at most ${OPERATOR_REQUEST_MAX_CHARS} Unicode characters`);
215
+ if (input.operatorRequestDigest !== operatorRequestDigest(input.operatorRequest)) errors.push("operatorRequestDigest does not match operatorRequest");
216
+ }
217
+ }
194
218
  for (const field of ["createdAt", "updatedAt"]) {
195
219
  if (typeof input[field] === "string" && !validTimestamp(input[field])) errors.push(`${field} must be a valid timestamp`);
196
220
  }
@@ -402,6 +426,8 @@ export function normalizeTaskContract(input, options = {}) {
402
426
  maxAttempts: positiveInteger(input.maxAttempts, DEFAULT_MAX_TASK_ATTEMPTS, 10),
403
427
  previousAttempts: Array.isArray(input.previousAttempts) ? input.previousAttempts.slice(-10).map((item) => pickFields(item, PREVIOUS_ATTEMPT_FIELDS)) : [],
404
428
  summary: typeof input.summary === "string" ? input.summary : "",
429
+ operatorRequest: typeof input.operatorRequest === "string" && input.operatorRequest.trim() ? input.operatorRequest : undefined,
430
+ operatorRequestDigest: typeof input.operatorRequestDigest === "string" && input.operatorRequestDigest.trim() ? input.operatorRequestDigest : undefined,
405
431
  riskLane: ["tiny", "normal", "high-risk"].includes(input.riskLane) ? input.riskLane : "normal",
406
432
  intakeMode: ["model", "runtime"].includes(input.intakeMode) ? input.intakeMode : "model",
407
433
  expectedOutput: typeof input.expectedOutput === "string" ? input.expectedOutput : "", acceptanceCriteria: stringArray(input.acceptanceCriteria),
@@ -413,7 +439,7 @@ export function normalizeTaskContract(input, options = {}) {
413
439
  memoryCitations: Array.isArray(input.memoryCitations) ? input.memoryCitations.map((item) => pickFields(item, CITATION_FIELDS)) : [],
414
440
  mcpCapabilities: stringArray(input.mcpCapabilities),
415
441
  verifyGroup: typeof input.verifyGroup === "string" && input.verifyGroup.trim() ? input.verifyGroup.trim() : undefined,
416
- verifyCommands: stringArray(input.verifyCommands),
442
+ verifyCommands: exactCommandArray(input.verifyCommands),
417
443
  workPlan: Array.isArray(input.workPlan) ? input.workPlan.map((item) => pickFields(item, WORK_PLAN_FIELDS)) : [],
418
444
  reviewLenses: stringArray(input.reviewLenses),
419
445
  acceptanceReceipt: normalizeAcceptanceReceipt(legacyDigestContract
@@ -691,6 +717,10 @@ function changedPathsFromNameStatus(output, observeRename) {
691
717
  }
692
718
 
693
719
  const WORKING_TREE_STREAM_CHUNK_BYTES = 1024 * 1024;
720
+ // This path cannot collide with enumerated project evidence because `.pi` is
721
+ // intentionally excluded from workspace discovery. Its unavailable digest
722
+ // turns a partial inventory into an explicitly non-proof-capable snapshot.
723
+ const WORKING_TREE_ENUMERATION_EVIDENCE_PATH = ".pi/piagent-state/.working-tree-enumeration-unavailable";
694
724
  function unavailableWorkingTreeDigest(file, reason, stat) {
695
725
  return unavailableWorkingTreeHash(crypto.createHash("sha256")
696
726
  .update(`${file}\0${reason}\0${stat?.mode ?? ""}\0${stat?.size ?? ""}\0${stat?.mtimeMs ?? ""}\0${stat?.ctimeMs ?? ""}`)
@@ -770,7 +800,18 @@ function streamedWorkingTreeFileDigest(root, file, hasHead, protectedProjectPath
770
800
  export function workingTreeSnapshot(cwd, options = {}) {
771
801
  const snapshot = {};
772
802
  const isProtectedProjectPath = typeof options.isProtectedProjectPath === "function" ? options.isProtectedProjectPath : () => false;
773
- const roots = gitEvidenceRoots(cwd);
803
+ let rootDetails;
804
+ try {
805
+ rootDetails = gitEvidenceRootDetails(cwd);
806
+ } catch (error) {
807
+ snapshot[WORKING_TREE_ENUMERATION_EVIDENCE_PATH] = unavailableWorkingTreeDigest(
808
+ WORKING_TREE_ENUMERATION_EVIDENCE_PATH,
809
+ `root-enumeration:${error?.code ?? "failed"}`
810
+ );
811
+ return snapshot;
812
+ }
813
+ const roots = rootDetails.roots;
814
+ const incompleteReasons = rootDetails.complete ? [] : ["root-enumeration-incomplete"];
774
815
  for (const root of roots) {
775
816
  const protectedAliases = new Set();
776
817
  const observeRename = (source, destination) => {
@@ -778,13 +819,33 @@ export function workingTreeSnapshot(cwd, options = {}) {
778
819
  if (paths.some((file) => file === ".pi/piagent-state" || file.startsWith(".pi/piagent-state/") || isProtectedProjectPath(prefixedGitPath(root.prefix, file))))
779
820
  paths.forEach((file) => protectedAliases.add(file));
780
821
  };
781
- const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
782
- const projectPath = prefixedGitPath(root.prefix, file);
783
- snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
822
+ try {
823
+ const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
824
+ const projectPath = prefixedGitPath(root.prefix, file);
825
+ snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
826
+ }
827
+ } catch {
828
+ incompleteReasons.push(`git-root-enumeration-incomplete:${root.prefix || "."}`);
784
829
  }
785
- } if (!isGitWorkingTree(cwd) && roots.length > 0) for (const file of nonGitWorkspaceFiles(cwd, roots.map((root) => root.prefix))) {
830
+ }
831
+ if (!roots.some((root) => root.prefix === "") && roots.length > 0) {
832
+ const looseDetails = nonGitWorkspaceFileDetails(
833
+ cwd,
834
+ roots.map((root) => root.prefix),
835
+ options.maxNonGitWorkspaceFiles
836
+ );
837
+ for (const file of looseDetails.files) {
786
838
  snapshot[file] = nonGitWorkspaceFileDigest(cwd, file, isProtectedProjectPath(file) ? file : null);
787
- } return snapshot;
839
+ }
840
+ if (!looseDetails.complete) incompleteReasons.push("non-git-enumeration-incomplete");
841
+ }
842
+ if (incompleteReasons.length > 0) {
843
+ snapshot[WORKING_TREE_ENUMERATION_EVIDENCE_PATH] = unavailableWorkingTreeDigest(
844
+ WORKING_TREE_ENUMERATION_EVIDENCE_PATH,
845
+ incompleteReasons.sort().join(",")
846
+ );
847
+ }
848
+ return snapshot;
788
849
  }
789
850
 
790
851
  export function taskStateMigrationStatus(cwd) {
@@ -13,8 +13,22 @@ import {
13
13
  } from "./failure-types.ts";
14
14
  import { isCurrentWorkingTreeDigest } from "./working-tree-digest.js";
15
15
 
16
+ export const MAX_VERIFY_COMMANDS = 2;
17
+ export const MAX_VERIFY_COMMAND_CHARS = 900;
18
+ export const MAX_VERIFY_COMMAND_BYTES = 900;
19
+ export const MAX_VERIFY_COMMAND_TOTAL_BYTES = MAX_VERIFY_COMMANDS * MAX_VERIFY_COMMAND_BYTES;
20
+ export const MAX_VERIFY_COMMAND_TOTAL_CHARS = MAX_VERIFY_COMMANDS * MAX_VERIFY_COMMAND_CHARS;
21
+
22
+ export function unicodeCodePointLength(value) {
23
+ return Array.from(String(value ?? "")).length;
24
+ }
25
+
26
+ export function utf8ByteLength(value) {
27
+ return Buffer.byteLength(String(value ?? ""), "utf8");
28
+ }
29
+
16
30
  function uniqueStrings(values) {
17
- return [...new Set((Array.isArray(values) ? values : []).filter((value) => typeof value === "string" && value.trim()).map((value) => value.trim()))];
31
+ return [...new Set((Array.isArray(values) ? values : []).filter((value) => typeof value === "string" && value.trim()))];
18
32
  }
19
33
 
20
34
  export function meaningfulVerificationCommands(commands = []) {
@@ -27,6 +41,25 @@ export function meaningfulVerificationCommands(commands = []) {
27
41
  });
28
42
  }
29
43
 
44
+ export function verifierCommandBoundsError(commands = []) {
45
+ const supplied = (Array.isArray(commands) ? commands : []).filter((command) => typeof command === "string");
46
+ if (supplied.some((command) => /[\r\n]/.test(command))) {
47
+ return "Exact verify commands must be single-line values without CR or LF characters; move multiline logic into a checked-in script before task intake.";
48
+ }
49
+ const normalized = uniqueStrings(supplied);
50
+ if (normalized.length > MAX_VERIFY_COMMANDS) {
51
+ return `Verify plans support at most ${MAX_VERIFY_COMMANDS} exact commands; consolidate or shorten the project verifier before task intake.`;
52
+ }
53
+ const oversized = normalized.find((command) => unicodeCodePointLength(command) > MAX_VERIFY_COMMAND_CHARS);
54
+ if (oversized) return `An exact verify command exceeds ${MAX_VERIFY_COMMAND_CHARS} characters; shorten it or move verifier logic into a checked-in script before task intake.`;
55
+ const oversizedTraffic = normalized.find((command) => utf8ByteLength(command) > MAX_VERIFY_COMMAND_BYTES);
56
+ if (oversizedTraffic) return `An exact verify command exceeds ${MAX_VERIFY_COMMAND_BYTES} UTF-8 bytes; shorten it or move verifier logic into a checked-in script before task intake.`;
57
+ if (normalized.reduce((total, command) => total + utf8ByteLength(command), 0) > MAX_VERIFY_COMMAND_TOTAL_BYTES) {
58
+ return `Exact verify commands exceed the ${MAX_VERIFY_COMMAND_TOTAL_BYTES}-byte aggregate limit; consolidate or shorten the project verifier before task intake.`;
59
+ }
60
+ return undefined;
61
+ }
62
+
30
63
  /** Return the last durably recorded observed execution for each exact command. */
31
64
  export function latestObservedVerificationEvidence(evidence = []) {
32
65
  const latest = new Map();
@@ -100,7 +133,10 @@ function parsedSignals(text, exitCode, structuredEvents) {
100
133
  const lower = text.toLowerCase();
101
134
  const signals = structuredEvents.map((event) => STRUCTURED_SIGNALS[event]).filter(Boolean);
102
135
  const add = (signal, pattern) => { if (pattern.test(lower)) signals.push(signal); };
103
- add("scope-diagnostic", /\b(outside (?:its |the )?declared scope|scope violation|protected path|symlink escape)\b/);
136
+ // Task scope is advisory. Only concrete filesystem-safety wording may be
137
+ // inferred from unstructured output; scope policy must arrive as a trusted
138
+ // structured event so ordinary assertion text cannot stop recovery.
139
+ add("scope-diagnostic", /\b(protected path|symlink escape)\b/);
104
140
  add("policy-diagnostic", /\b(policy (?:blocked|denied|violation)|capability lock|guard blocked)\b/);
105
141
  add("permission-diagnostic", /\b(eacces|eperm|permission denied|not authorized|unauthorized|forbidden)\b/);
106
142
  add("provider-rate-limit", /\b(?:http\s*)?429\b|\brate[ -]?limit(?:ed|ing)?\b/);
@@ -191,7 +227,7 @@ const RECORDED_CATEGORY_HINTS = Object.freeze({
191
227
  environment: "command not found in runtime environment",
192
228
  "provider-network": "provider API request timed out",
193
229
  "permission-policy": "permission denied by policy",
194
- "scope-protected-path": "outside declared scope",
230
+ "scope-protected-path": "protected path is forbidden",
195
231
  "flaky-infrastructure": "EADDRINUSE: port is already in use"
196
232
  });
197
233
 
@@ -202,16 +238,15 @@ export function classifyRecordedVerificationFailure(summary, exitCode = 1) {
202
238
  return classifyVerificationFailure(hint ? `${text}\n${hint}` : text, exitCode);
203
239
  }
204
240
 
205
- const COMPLETION_SCOPE_BOUNDARY = /(?:^changes within task scope\b|^read-only task has observed changes\b|\boutside (?:(?:its|the|task) )?(?:declared )?scope\b|\bscope violation\b|\bprotected(?:\/read-only| or read-only)? paths?\b|\bread-only path\b)/i;
241
+ const COMPLETION_POLICY_BOUNDARY = /(?:^read-only task has observed changes\b|^mutation-forbidden task has observed changes\b|\bprotected(?:\/read-only| or read-only)? paths?\b|\bread-only path\b)/i;
206
242
 
207
243
  export function classifyCompletionGateFailure(missing = [], summary = "", exitCode = 1) {
208
244
  const missingItems = Array.isArray(missing) ? missing.map((item) => String(item)) : [];
209
- // Scope, protected-path, and read-only mutation boundaries are structural
210
- // completion failures. They must outrank verifier/acceptance diagnostics and
211
- // remain non-retryable even when the latest exact verifier exited zero.
212
- if (missingItems.some((item) => COMPLETION_SCOPE_BOUNDARY.test(item))) {
213
- return classifyVerificationFailure("Completion gate rejected a scope or protected-path boundary.", 1, {
214
- structuredEvents: ["scope-violation"]
245
+ // Protected-path and read-only mutation boundaries are structural
246
+ // completion failures. Task scope is advisory and never enters this path.
247
+ if (missingItems.some((item) => COMPLETION_POLICY_BOUNDARY.test(item))) {
248
+ return classifyVerificationFailure("Completion gate rejected a protected-path or read-only boundary.", 1, {
249
+ structuredEvents: ["protected-path"]
215
250
  });
216
251
  }
217
252
  if (missingItems.some((item) => /^critical acceptance evidence\b/i.test(item))) {
@@ -235,8 +270,8 @@ export function chooseVerificationScope(profileVerifyCommands = {}, changedFiles
235
270
  || /^(?:README|CHANGELOG|CONTRIBUTING|SECURITY)(?:\.|$)/i.test(file);
236
271
  const hasDocs = files.some(isDocumentationFile);
237
272
  const hasDocsOnly = files.length > 0 && files.every(isDocumentationFile);
238
- const hasFrontend = files.some((file) => /(^|\/)(?:app|pages|components|frontend|web|client)(\/|$)|\.(?:tsx|jsx|css|scss|vue|svelte)$/i.test(file));
239
- const hasBackend = files.some((file) => /(^|\/)(?:api|server|backend|services)(\/|$)|\.(?:go|py|rs|java|kt|cs|rb|php|sql)$/i.test(file));
273
+ const hasFrontend = files.some((file) => /(^|[\/_-])(?:app|pages|components|frontend|web|client)(?=[\/_-]|$)|\.(?:tsx|jsx|css|scss|vue|svelte)$/i.test(file));
274
+ const hasBackend = files.some((file) => /(^|[\/_-])(?:api|server|backend|services)(?=[\/_-]|$)|\.(?:go|py|rs|java|kt|cs|rb|php|sql)$/i.test(file));
240
275
  const selectedGroups = [];
241
276
  if (hasDocsOnly && groups.docs) {
242
277
  selectedGroups.push("docs");
@@ -310,5 +345,7 @@ export function selectVerificationPlan(profile, requestedGroup, changeMode, cwd,
310
345
  error: `Verify group ${group ?? "(none)"} has no meaningful command. Configure .pi/piagent-profile.json before source changes.`
311
346
  };
312
347
  }
348
+ const boundsError = verifierCommandBoundsError(commands);
349
+ if (boundsError) return { group, commands, error: boundsError };
313
350
  return { group, commands };
314
351
  }
@@ -2,14 +2,35 @@ import { execFileSync } from "node:child_process";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
 
5
- const WORKSPACE_EVIDENCE_MAX_FILES = 2000;
5
+ // Parent workspaces commonly carry shared plans, verifier scripts, and agent
6
+ // configuration beside their child repositories. Keep the traversal bounded,
7
+ // but leave enough room for a real multi-repository workspace instead of
8
+ // silently dropping evidence at the former 2,000-file ceiling.
9
+ const WORKSPACE_EVIDENCE_MAX_FILES = 20_000;
10
+ // These names are not merely noisy build output: they carry repository
11
+ // ownership, Piagent authority, or provider credentials. Evidence discovery
12
+ // excludes them and the mutation guard protects them independently of task
13
+ // focus, so broad monorepo access cannot create an unaudited private-state gap.
14
+ export const WORKSPACE_PRIVATE_STATE_PATTERNS = Object.freeze([
15
+ "**/.git/**", "**/.hg/**", "**/.svn/**", "**/.pi/**",
16
+ "**/.aws/**", "**/.azure/**", "**/.docker/**", "**/.gnupg/**", "**/.kube/**", "**/.ssh/**",
17
+ "**/.git-credentials", "**/.netrc", "**/.npmrc", "**/.pypirc",
18
+ "**/.env", "**/.env.*",
19
+ "**/auth.json", "**/credential.json", "**/credentials.json", "**/secret.json", "**/secrets.json",
20
+ "**/token.json", "**/tokens.json"
21
+ ]);
6
22
  const WORKSPACE_EVIDENCE_SKIP_NAMES = new Set([
7
- ".git", ".hg", ".svn", ".pi", "node_modules", "dist", "build", ".next", "coverage"
23
+ ".git", ".hg", ".svn", ".pi", "node_modules", "dist", "build", ".next", "coverage",
24
+ ".cache", ".npm", ".pnpm-store", ".yarn", ".turbo", ".aws", ".azure", ".docker",
25
+ ".gnupg", ".kube", ".ssh", ".ds_store", ".git-credentials", ".netrc", ".npmrc", ".pypirc"
8
26
  ]);
9
27
  const WORKSPACE_EVIDENCE_PRIORITY_NAMES = new Map([["plans", 0]]);
10
28
 
11
- function hiddenWorkspaceEvidenceSegment(value) {
12
- return String(value ?? "").startsWith(".");
29
+ function excludedWorkspaceEvidenceName(value) {
30
+ const name = String(value ?? "");
31
+ return WORKSPACE_EVIDENCE_SKIP_NAMES.has(name.toLowerCase())
32
+ || /^\.env(?:\.|$)/i.test(name)
33
+ || /^(?:auth|credentials?|secrets?|tokens?)\.json$/i.test(name);
13
34
  }
14
35
 
15
36
  export function gitOutput(cwd, args, options = {}) {
@@ -86,6 +107,15 @@ export function directChildGitEvidenceRoots(cwd) {
86
107
 
87
108
  export function gitEvidenceRootDetails(cwd) {
88
109
  if (isGitWorkingTree(cwd)) return { roots: [{ cwd, prefix: "" }], complete: true };
110
+ // A present Git marker combined with a failed Git probe is ambiguous (for
111
+ // example a broken worktree pointer or a transient command failure). Do not
112
+ // reinterpret that repository as an ordinary complete non-Git directory.
113
+ try {
114
+ fs.lstatSync(path.join(cwd, ".git"));
115
+ return { roots: [], complete: false };
116
+ } catch (error) {
117
+ if (error?.code !== "ENOENT") return { roots: [], complete: false };
118
+ }
89
119
  return directChildGitEvidenceRootDetails(cwd);
90
120
  }
91
121
 
@@ -93,9 +123,16 @@ export function gitEvidenceRoots(cwd) {
93
123
  return gitEvidenceRootDetails(cwd).roots;
94
124
  }
95
125
 
96
- export function nonGitWorkspaceFileDetails(cwd, gitRootPrefixes) {
126
+ function workspaceEvidenceFileLimit(value) {
127
+ return Number.isInteger(value)
128
+ ? Math.max(1, Math.min(WORKSPACE_EVIDENCE_MAX_FILES, value))
129
+ : WORKSPACE_EVIDENCE_MAX_FILES;
130
+ }
131
+
132
+ export function nonGitWorkspaceFileDetails(cwd, gitRootPrefixes, maxFiles = WORKSPACE_EVIDENCE_MAX_FILES) {
97
133
  const files = [];
98
134
  let complete = true;
135
+ const limit = workspaceEvidenceFileLimit(maxFiles);
99
136
  const gitRoots = new Set(gitRootPrefixes.filter(Boolean));
100
137
  const orderedEntries = (entries) => [...entries].sort((left, right) => {
101
138
  const leftPriority = WORKSPACE_EVIDENCE_PRIORITY_NAMES.get(left.name.toLowerCase()) ?? 10;
@@ -103,7 +140,7 @@ export function nonGitWorkspaceFileDetails(cwd, gitRootPrefixes) {
103
140
  return leftPriority - rightPriority || left.name.localeCompare(right.name, "en-US");
104
141
  });
105
142
  function visit(directory, relativeDirectory) {
106
- if (files.length >= WORKSPACE_EVIDENCE_MAX_FILES) {
143
+ if (files.length >= limit) {
107
144
  complete = false;
108
145
  return;
109
146
  }
@@ -115,15 +152,15 @@ export function nonGitWorkspaceFileDetails(cwd, gitRootPrefixes) {
115
152
  return;
116
153
  }
117
154
  for (const entry of orderedEntries(entries)) {
118
- if (files.length >= WORKSPACE_EVIDENCE_MAX_FILES) {
155
+ if (files.length >= limit) {
119
156
  complete = false;
120
157
  return;
121
158
  }
122
- // Loose files beside nested repositories are not governed by either
123
- // repository's ignore/trust policy. Keep hidden editor, agent, cloud,
124
- // credential, and cache state out of both manifests and change evidence
125
- // instead of attempting to maintain an incomplete name blacklist.
126
- if (WORKSPACE_EVIDENCE_SKIP_NAMES.has(entry.name) || hiddenWorkspaceEvidenceSegment(entry.name)) continue;
159
+ // A parent workspace can own real project source in hidden directories
160
+ // (for example shared verifier scripts). Evidence discovery therefore
161
+ // excludes explicit private/cache/credential names instead of treating
162
+ // every dot-prefixed path as non-project state.
163
+ if (excludedWorkspaceEvidenceName(entry.name)) continue;
127
164
  const relative = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
128
165
  if (gitRoots.has(relative.split("/")[0])) continue;
129
166
  const absolute = path.join(directory, entry.name);
@@ -141,8 +178,8 @@ export function nonGitWorkspaceFileDetails(cwd, gitRootPrefixes) {
141
178
  return { files: files.sort(), complete };
142
179
  }
143
180
 
144
- export function nonGitWorkspaceFiles(cwd, gitRootPrefixes) {
145
- return nonGitWorkspaceFileDetails(cwd, gitRootPrefixes).files;
181
+ export function nonGitWorkspaceFiles(cwd, gitRootPrefixes, maxFiles = WORKSPACE_EVIDENCE_MAX_FILES) {
182
+ return nonGitWorkspaceFileDetails(cwd, gitRootPrefixes, maxFiles).files;
146
183
  }
147
184
 
148
185
  export function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes) {
@@ -150,8 +187,7 @@ export function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes
150
187
  const topSegment = segments[0];
151
188
  return Boolean(
152
189
  topSegment
153
- && !WORKSPACE_EVIDENCE_SKIP_NAMES.has(topSegment)
154
- && !segments.some(hiddenWorkspaceEvidenceSegment)
190
+ && !segments.some(excludedWorkspaceEvidenceName)
155
191
  && !gitRootPrefixes.includes(topSegment)
156
192
  );
157
193
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -3,7 +3,27 @@
3
3
  "name": "piagent-base-policy",
4
4
  "protectedPaths": [
5
5
  ".git/**",
6
+ "**/.git/**",
7
+ "**/.hg/**",
8
+ "**/.svn/**",
9
+ "**/.pi/**",
10
+ "**/.aws/**",
11
+ "**/.azure/**",
12
+ "**/.docker/**",
13
+ "**/.gnupg/**",
14
+ "**/.kube/**",
15
+ "**/.ssh/**",
16
+ "**/.git-credentials",
17
+ "**/.netrc",
18
+ "**/.npmrc",
19
+ "**/.pypirc",
6
20
  "**/auth.json",
21
+ "**/credential.json",
22
+ "**/credentials.json",
23
+ "**/secret.json",
24
+ "**/secrets.json",
25
+ "**/token.json",
26
+ "**/tokens.json",
7
27
  "**/.env",
8
28
  "**/.env.*",
9
29
  "**/node_modules/**",
@@ -16,7 +36,27 @@
16
36
  ],
17
37
  "shellProtectedPaths": [
18
38
  ".git/**",
39
+ "**/.git/**",
40
+ "**/.hg/**",
41
+ "**/.svn/**",
42
+ "**/.pi/**",
43
+ "**/.aws/**",
44
+ "**/.azure/**",
45
+ "**/.docker/**",
46
+ "**/.gnupg/**",
47
+ "**/.kube/**",
48
+ "**/.ssh/**",
49
+ "**/.git-credentials",
50
+ "**/.netrc",
51
+ "**/.npmrc",
52
+ "**/.pypirc",
19
53
  "**/auth.json",
54
+ "**/credential.json",
55
+ "**/credentials.json",
56
+ "**/secret.json",
57
+ "**/secrets.json",
58
+ "**/token.json",
59
+ "**/tokens.json",
20
60
  "**/.env",
21
61
  "**/.env.*",
22
62
  ".pi/piagent-state/**",
@@ -232,7 +272,7 @@
232
272
  },
233
273
  "orchestrationPolicy": {
234
274
  "defaultMode": "solo-first",
235
- "maxConcurrentSubagents": 2,
275
+ "maxConcurrentSubagents": 1,
236
276
  "defaultReviewLenses": [
237
277
  "correctness",
238
278
  "tests",
@@ -240,7 +280,7 @@
240
280
  ],
241
281
  "roleModelGuidance": {
242
282
  "planner": "Use the strongest available model for decomposition, architecture, risk, and acceptance criteria.",
243
- "worker": "Use the fastest reliable model for a bounded, already-planned single write set.",
283
+ "worker": "Disabled by default. The parent model owns all implementation and mutation.",
244
284
  "reviewer": "Use a model or thinking setting decorrelated from the worker when review quality matters.",
245
285
  "watchdog": "Use a strong model only for final risk review, security, release, or high-impact changes."
246
286
  },
@@ -34,7 +34,7 @@
34
34
  "CAP-11": "advisory",
35
35
  "CAP-12": "on",
36
36
  "CAP-13": "off",
37
- "CAP-14": "recommend",
37
+ "CAP-14": "off",
38
38
  "CAP-15": "off",
39
39
  "CAP-16": "on",
40
40
  "CAP-17": "on"
@@ -80,7 +80,7 @@
80
80
  "CAP-11": "advisory",
81
81
  "CAP-12": "on",
82
82
  "CAP-13": "strict",
83
- "CAP-14": "recommend",
83
+ "CAP-14": "off",
84
84
  "CAP-15": "off",
85
85
  "CAP-16": "on",
86
86
  "CAP-17": "on"
@@ -314,7 +314,7 @@
314
314
  "layer": "L3",
315
315
  "configKey": "helpers",
316
316
  "configSources": ["manifest", "PIAGENT_HELPERS_MODE"],
317
- "defaultMode": "recommend",
317
+ "defaultMode": "off",
318
318
  "killSwitchMode": "off",
319
319
  "dependencies": ["CAP-02", "CAP-05", "CAP-08"],
320
320
  "modeMappings": [
@@ -26,13 +26,7 @@ Flow:
26
26
  - BE scout: read-only.
27
27
  - FE implementation: source-write.
28
28
  - Auth/data migration/external provider: high-risk, ask before implementation.
29
- 4. Use bounded read-only subagents only when BE/FE mapping has independent lanes worth the extra token/tool cost:
30
- - `piagent-scout` maps backend contract read-only;
31
- - `piagent-scout` maps frontend touchpoints read-only;
32
- - builtin `context-builder` may create handoff context when the BE→FE mapping touches multiple journeys/forms/contracts;
33
- - `piagent-planner` produces the FE implementation plan when the contract touches multiple layers;
34
- - `piagent-reviewer` reviews diff/verification after implementation.
35
- Continue single-agent if subagents are unavailable, the task is tiny, or requirements are unresolved.
29
+ 4. Keep BE scouting and FE implementation in the parent by default. At most one fresh read-only `piagent-scout` may map either the BE contract or FE touchpoints only when runtime evidence proves two independent lanes and at least 30% projected total token savings after handoff/merge. Never launch two scouts, delegate FE writes/planning/review, inherit the parent transcript, or retry a deterministic helper failure.
36
30
  5. Scout backend contract read-only:
37
31
  - controller/route/handler;
38
32
  - request/response DTO/schema;
@@ -53,7 +47,7 @@ Flow:
53
47
  - route/page/component/form;
54
48
  - tests/e2e.
55
49
  9. Call `piagent_task_start` exactly once before FE source writes and reuse an active contract.
56
- 10. Implement FE only with one writer by default.
50
+ 10. Implement FE in the parent; helper workers are disabled.
57
51
  11. Run every exact command returned in `task.verifyCommands` after the latest mutation. For a normal default plan, review the final diff and mark only its review step done with `piagent_task_progress`.
58
52
  12. Let runtime hooks record context, verification, trace, and final-gate state. Use recovery tools only if the runtime reports missing evidence.
59
53
  13. Do not paste full test/build/tool logs into chat or final output. Summarize the signal and quote only relevant failing lines.
@@ -17,7 +17,7 @@ Rules:
17
17
  4. Read `.pi/project-context.md`; if it is missing or still pending, recommend `/onboard-project run` before implementation.
18
18
  5. Read required context.
19
19
  6. Identify protected paths and external/high-risk actions.
20
- 7. Decide whether subagents are useful enough to justify extra token/tool cost. If the bundled `pi-subagents` parent skill is available, use it for delegation patterns and safety boundaries. If `pi-subagents`/`subagent(...)` is available, use `piagent-scout` for independent read-only repo/spec mapping before medium/large plans. Use builtin `context-builder` when the task needs a handoff context/meta-prompt. Use `piagent-oracle` for architecture/risk challenge when the decision is non-obvious. Continue single-agent for tiny plans or unavailable subagent tooling.
20
+ 7. Keep planning in the parent. At most one fresh read-only helper may run only when runtime evidence proves two independent workstreams, no dependency on later parent output, and at least 30% projected total token savings after transfer/merge. Never delegate implementation, fork parent history, retry a deterministic helper failure, or add a helper only for review quality.
21
21
  8. Produce a compact task tree/workPlan with exact files, commands, owner role, review lenses, and verify gates.
22
22
  9. Do not implement yet unless the user explicitly asks.
23
23
 
@@ -23,18 +23,14 @@ Flow:
23
23
  1. Use runtime automatic intake for a bounded platform change. If runtime pauses for broad, high-risk, or ambiguous scope, call `piagent_task_start` exactly once with project-relative path/glob scope and reuse an active contract.
24
24
  2. Read the narrow current implementation, its tests, and the relevant policy/schema/docs. Runtime hooks inject bounded navigation context when useful.
25
25
  3. Treat memory and generated context as advisory; verify against current files.
26
- 4. Use bounded subagents only when independent lanes justify their extra token/tool cost:
27
- - use `piagent-scout` for read-only code/package mapping;
28
- - use `piagent-planner` for a plan when multiple modules are affected;
29
- - use `piagent-reviewer` for diff, docs, and verification review;
30
- - continue single-agent for small, localized edits.
26
+ 4. Keep platform analysis, planning, implementation, and review in the parent. At most one fresh read-only helper may map a genuinely independent lane only when runtime evidence projects at least 30% lower total tokens after handoff/merge. Do not delegate writes, fork parent history, run parallel helpers, or retry deterministic runtime/scope failures.
31
27
  5. If the task needs external repository context provided by the user, use a targeted read-only checkout when available.
32
28
  6. Produce an implementation matrix only when multiple platform areas are affected:
33
29
 
34
30
  | Area | Current behavior | Target behavior | Files/config | Verification |
35
31
  |---|---|---|---|---|
36
32
 
37
- 7. Implement only the bounded target behavior with one writer by default.
33
+ 7. Implement only the bounded target behavior in the parent.
38
34
  8. If runtime behavior changes, update README/docs and add or adjust a decision note when appropriate.
39
35
  9. Run every exact command returned in `task.verifyCommands` after the latest mutation. For a normal default plan, review the final diff and mark only its review step done with `piagent_task_progress`.
40
36
  10. Let runtime hooks record context, verification, trace, and final-gate state. Use recovery tools only if the runtime reports missing evidence.
@@ -14,13 +14,8 @@ Rules:
14
14
  1. Read the current diff and the narrow surrounding source/tests. Runtime hooks inject bounded navigation context when useful.
15
15
  2. Select review lenses from the actual change risk; do not load orchestration or diagnostic tools just to choose them.
16
16
  3. Stay read-only unless explicitly asked to write a report.
17
- 4. Check protected paths, scope drift, acceptance behavior, and verify-command coverage.
18
- 5. For a non-trivial diff, use bounded read-only reviewers only when independent lanes justify their token cost:
19
- - correctness/edge cases;
20
- - tests/verification;
21
- - scope drift/protected paths;
22
- - security/high-risk only when relevant.
23
- If the user asks for a loop, use `/review-loop` semantics or equivalent parent-controlled max-round loop. Continue single-agent for tiny diffs or unavailable subagent tooling.
17
+ 4. Check protected paths, unrelated change drift, acceptance behavior, and verify-command coverage. Treat task scope as an initial focus, not write authority.
18
+ 5. Review directly in the parent. A non-trivial diff does not itself justify another model run. At most one fresh read-only reviewer is eligible only when a runtime estimate proves an independent lane and at least 30% projected total token savings after handoff/merge; never run parallel review or retry a deterministic helper failure. If the user explicitly asks for a loop, keep it parent-controlled and bounded.
24
19
  6. Report findings by severity.
25
20
  7. Do not call diagnostic Piagent tools for routine review and do not paste full diff/test/tool logs. Summarize the signal and quote only relevant lines.
26
21
 
@@ -15,7 +15,7 @@ Lean scouting flow:
15
15
 
16
16
  1. Read targeted current files and return cited evidence. Runtime hooks inject bounded navigation context when useful and enforce read boundaries.
17
17
  2. Create a read-only contract with `piagent_task_start` only for a broad governed scout that needs persisted scope or handoff. Tiny default scouts complete from observed reads; for a normal default scout, review the evidence and mark only the returned review step done with `piagent_task_progress`.
18
- 3. Use memory, vendor documentation, or a read-only subagent only when current source is insufficient or the work has independent lanes. Verify advisory material against current files.
18
+ 3. Use memory or vendor documentation only when current source is insufficient. Keep scouting in the parent; a helper requires separate runtime proof of two independent lanes and at least 30% projected net token saving.
19
19
  4. A low-confidence search may trigger one bounded finder pass. Stop broad search when the requested evidence is covered.
20
20
  5. Do not call diagnostic Piagent tools for a routine scout. Load them only when the runtime reports a recovery need or the operator explicitly asks for diagnostics.
21
21
  6. Do not edit source or paste full command output. Summarize relevant evidence and unknowns.