@hassangameryt/freeflow 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (290) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/README.md +96 -149
  7. package/capabilities/cognitive-routing/SKILL.md +21 -129
  8. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  9. package/capabilities/cognitive-routing/references/worker.md +38 -0
  10. package/capabilities/compaction/SKILL.md +3 -3
  11. package/capabilities/compaction/references/summary-format.md +26 -15
  12. package/capabilities/messaging/SKILL.md +49 -0
  13. package/capabilities/task-memory/SKILL.md +87 -0
  14. package/command-surface.json +6 -54
  15. package/gemini-extension.json +1 -1
  16. package/hooks/claude/hooks.json +13 -3
  17. package/hooks/codex/hooks.json +2 -1
  18. package/hooks/shared/runtime-context.mjs +69 -77
  19. package/package.json +25 -8
  20. package/pi-extension/dist/ask-user/ask.js +108 -0
  21. package/pi-extension/dist/ask-user/form.js +250 -0
  22. package/pi-extension/dist/ask-user/questions.js +66 -0
  23. package/pi-extension/dist/ask-user/tool.js +124 -0
  24. package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
  25. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  26. package/pi-extension/dist/cognitive-routing/config.js +3 -4
  27. package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
  28. package/pi-extension/dist/cognitive-routing/gate.js +20 -5
  29. package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
  30. package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
  31. package/pi-extension/dist/cognitive-routing/projection.js +23 -6
  32. package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
  33. package/pi-extension/dist/cognitive-routing/render.js +15 -15
  34. package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
  35. package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
  36. package/pi-extension/dist/cognitive-routing/session.js +3 -1
  37. package/pi-extension/dist/cognitive-routing/sources.js +29 -4
  38. package/pi-extension/dist/cognitive-routing/state.js +18 -8
  39. package/pi-extension/dist/cognitive-routing/status.js +4 -3
  40. package/pi-extension/dist/cognitive-routing/tools.js +29 -15
  41. package/pi-extension/dist/cognitive-routing/types.js +10 -1
  42. package/pi-extension/dist/compaction/carry.js +36 -22
  43. package/pi-extension/dist/compaction/config.js +4 -2
  44. package/pi-extension/dist/compaction/controller.js +52 -29
  45. package/pi-extension/dist/compaction/harness.js +19 -3
  46. package/pi-extension/dist/compaction/results.js +14 -13
  47. package/pi-extension/dist/compaction/tool.js +6 -6
  48. package/pi-extension/dist/host/catalog.js +20 -35
  49. package/pi-extension/dist/host/config-validation.js +233 -0
  50. package/pi-extension/dist/host/config.js +78 -91
  51. package/pi-extension/dist/host/inbound.js +142 -0
  52. package/pi-extension/dist/host/projection-tags.js +1 -1
  53. package/pi-extension/dist/host/prompts.js +24 -13
  54. package/pi-extension/dist/host/read-only-session.js +6 -1
  55. package/pi-extension/dist/host/request-history.js +7 -3
  56. package/pi-extension/dist/host/rows.js +25 -0
  57. package/pi-extension/dist/host/runtime-state.js +17 -2
  58. package/pi-extension/dist/host/section-keeper.js +85 -0
  59. package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
  60. package/pi-extension/dist/host/settings/settings-view.js +27 -8
  61. package/pi-extension/dist/host/status.js +19 -5
  62. package/pi-extension/dist/index.js +638 -72
  63. package/pi-extension/dist/messaging/config.js +27 -0
  64. package/pi-extension/dist/messaging/messaging.js +398 -0
  65. package/pi-extension/dist/messaging/registry.js +57 -0
  66. package/pi-extension/dist/messaging/render.js +90 -0
  67. package/pi-extension/dist/messaging/tools.js +109 -0
  68. package/pi-extension/dist/messaging/transport.js +101 -0
  69. package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
  70. package/pi-extension/dist/task-memory/config.js +49 -0
  71. package/pi-extension/dist/task-memory/registry.js +119 -0
  72. package/pi-extension/dist/task-memory/render.js +107 -0
  73. package/pi-extension/dist/task-memory/runtime.js +994 -0
  74. package/pi-extension/dist/task-memory/tool.js +233 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
  76. package/pi-extension/dist/tool-execution/background.js +220 -28
  77. package/pi-extension/dist/tool-execution/config.js +14 -3
  78. package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
  79. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  80. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  81. package/pi-extension/dist/tool-execution/tools.js +2 -2
  82. package/runtime/prompts/cognitive-routing.md +4 -4
  83. package/runtime/prompts/core.md +24 -14
  84. package/runtime/prompts/interaction-contract.md +7 -4
  85. package/runtime/prompts/tool-execution.md +3 -16
  86. package/runtime/prompts/working-method.md +16 -99
  87. package/skills/deliver-work/SKILL.md +76 -0
  88. package/skills/deliver-work/references/branch-closeout.md +134 -0
  89. package/skills/deliver-work/references/commit.md +83 -0
  90. package/skills/deliver-work/references/launch.md +94 -0
  91. package/skills/deliver-work/references/release.md +85 -0
  92. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  93. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  94. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  95. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  96. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  97. package/skills/discuss-work/SKILL.md +148 -0
  98. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
  99. package/skills/discuss-work/references/learning-action.md +36 -0
  100. package/skills/discuss-work/scripts/choices.mjs +101 -0
  101. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  102. package/skills/evaluate-behavior/SKILL.md +107 -0
  103. package/skills/evaluate-behavior/references/authoring.md +381 -0
  104. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  105. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  106. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  107. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  108. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  109. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  110. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  111. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  112. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  113. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  114. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  115. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  116. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  117. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  118. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  119. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  120. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  121. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  122. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  123. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  124. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  125. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  126. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  127. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  128. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  129. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  130. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  131. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  132. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  133. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  134. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  135. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  136. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  137. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  138. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  139. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  140. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  141. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  142. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  143. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  144. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  145. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  146. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  147. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  148. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  149. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  150. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  151. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  152. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  153. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  154. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
  156. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  157. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  158. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  159. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  160. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  161. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  162. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
  163. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  164. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  165. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  166. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  167. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
  168. package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
  169. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  170. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  171. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  172. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  173. package/skills/execute-work/SKILL.md +96 -104
  174. package/skills/execute-work/references/code-practices.md +27 -51
  175. package/skills/execute-work/references/design.md +271 -0
  176. package/skills/execute-work/references/diagnose.md +117 -0
  177. package/skills/execute-work/references/migration.md +108 -0
  178. package/skills/execute-work/references/simplify.md +65 -0
  179. package/skills/execute-work/scripts/self-review.mjs +422 -0
  180. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  181. package/skills/review-work/SKILL.md +89 -65
  182. package/skills/review-work/references/adjudication.md +94 -0
  183. package/skills/review-work/references/artifact.md +198 -0
  184. package/skills/review-work/references/evals-and-judges.md +66 -0
  185. package/skills/review-work/references/work.md +177 -0
  186. package/skills/review-work/scripts/check-brief.mjs +128 -0
  187. package/skills/review-work/scripts/review-packet.mjs +233 -0
  188. package/skills/setup-freeflow/SKILL.md +68 -17
  189. package/skills/track-work/SKILL.md +52 -20
  190. package/skills/track-work/references/handoff.md +190 -0
  191. package/skills/track-work/references/projects.md +51 -0
  192. package/skills/track-work/references/working-record-format.md +36 -18
  193. package/skills/track-work/scripts/lib/document.mjs +17 -13
  194. package/skills/track-work/scripts/lib/format.mjs +2 -0
  195. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  196. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  197. package/skills/track-work/scripts/lib/search.mjs +40 -0
  198. package/skills/track-work/scripts/lib/store.mjs +46 -15
  199. package/skills/track-work/scripts/lib/views.mjs +50 -1
  200. package/skills/track-work/scripts/working-record.mjs +232 -13
  201. package/skills/verify-work/SKILL.md +45 -37
  202. package/skills/verify-work/references/feature-map.md +120 -0
  203. package/skills/verify-work/references/runtime-evidence.md +203 -0
  204. package/skills/verify-work/references/test-design.md +25 -6
  205. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  206. package/skills/write-docs/SKILL.md +113 -25
  207. package/skills/write-docs/references/documentation-shapes.md +68 -3
  208. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  209. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  210. package/skills/write-docs/scripts/docs-check.mjs +96 -6
  211. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  212. package/skills/write-skill/SKILL.md +92 -52
  213. package/skills/write-skill/references/activation-and-wording.md +143 -0
  214. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  215. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  216. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  217. package/skills/write-skill/scripts/skill-author.mjs +51 -8
  218. package/capabilities/cognitive-routing/references/both-mode.md +0 -36
  219. package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
  220. package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
  221. package/plugin.json +0 -33
  222. package/skills/action-selection/SKILL.md +0 -92
  223. package/skills/action-selection/references/trajectory-stalls.md +0 -73
  224. package/skills/bypass/SKILL.md +0 -153
  225. package/skills/commit-work/SKILL.md +0 -93
  226. package/skills/commit-work/references/git-state-edges.md +0 -94
  227. package/skills/commit-work/references/simple-push.md +0 -94
  228. package/skills/commit-work/references/staging-decisions.md +0 -106
  229. package/skills/decision-gate/SKILL.md +0 -87
  230. package/skills/design-for-depth/SKILL.md +0 -108
  231. package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
  232. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  233. package/skills/design-for-depth/references/interface-design-loop.md +0 -105
  234. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
  235. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
  236. package/skills/diagnose-failure/SKILL.md +0 -140
  237. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
  238. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  239. package/skills/discuss/SKILL.md +0 -140
  240. package/skills/evaluate-skill/SKILL.md +0 -188
  241. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
  242. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  243. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
  244. package/skills/evaluate-skill/references/review-and-revision.md +0 -94
  245. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
  246. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  247. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  248. package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
  249. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
  250. package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
  251. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
  252. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
  253. package/skills/execute-work/references/execute-work-edges.md +0 -78
  254. package/skills/finish-branch/SKILL.md +0 -117
  255. package/skills/finish-branch/references/integration-options.md +0 -80
  256. package/skills/handoff/SKILL.md +0 -82
  257. package/skills/handoff/references/handoff-templates.md +0 -150
  258. package/skills/launch-work/SKILL.md +0 -116
  259. package/skills/launch-work/references/launch-readiness.md +0 -85
  260. package/skills/migration-work/SKILL.md +0 -115
  261. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  262. package/skills/release-work/SKILL.md +0 -118
  263. package/skills/release-work/references/release-evidence.md +0 -78
  264. package/skills/review-artifact/SKILL.md +0 -143
  265. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  266. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  267. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  268. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  269. package/skills/review-work/references/security-risk-lens.md +0 -67
  270. package/skills/setup-freeflow/references/activation-contract.md +0 -103
  271. package/skills/setup-freeflow/references/host-setup.md +0 -67
  272. package/skills/simplify-code/SKILL.md +0 -85
  273. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  274. package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
  275. package/skills/verify-work/references/integration-evidence.md +0 -49
  276. package/skills/verify-work/references/performance-evidence.md +0 -59
  277. package/skills/workflow/SKILL.md +0 -171
  278. package/skills/workflow/references/domain-skill-composition.md +0 -45
  279. package/skills/write-plan/SKILL.md +0 -92
  280. package/skills/write-plan/references/plan-shapes.md +0 -156
  281. package/skills/write-skill/references/activation-boundaries.md +0 -106
  282. package/skills/write-skill/references/agent-first-instructions.md +0 -133
  283. package/skills/write-skill/references/progressive-disclosure.md +0 -129
  284. package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
  285. package/skills/write-spec/SKILL.md +0 -85
  286. package/skills/write-spec/references/artifact-standards.md +0 -86
  287. package/skills/write-spec/references/decision-records.md +0 -80
  288. package/skills/write-spec/references/spec-shapes.md +0 -143
  289. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  290. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -2,27 +2,34 @@ import { ROUTING_TOOLS } from "./types.js";
2
2
  import { ROUTING_SCHEMAS, explainRoutingArguments, matches } from "./schemas.js";
3
3
  export { ROUTING_SCHEMAS } from "./schemas.js";
4
4
  import { renderRoutingCall, renderRoutingResult } from "./render.js";
5
+ /** What Pi shows for each tool, as the other Freeflow tools name theirs. */
6
+ const ROUTING_LABELS = {
7
+ delegate_work: "Delegate",
8
+ return_work: "Return",
9
+ routing_unit: "Routing unit",
10
+ select_evidence: "Evidence",
11
+ };
5
12
  const descriptions = {
6
- freeflow_delegate:
13
+ delegate_work:
7
14
  "Save a supported assignment contract and request enabled worker execution. Explicit replace supersedes a quiescent outstanding assignment in the same unit.",
8
- freeflow_return:
15
+ return_work:
9
16
  "Save or revise the actual assignment report, save an attached recovery supplement, or retry the current saved return without resubmitting text. Returning does not accept or close the unit.",
10
- freeflow_unit:
17
+ routing_unit:
11
18
  "Inspect current responsibility, paginated work history, or exact saved communication. Coordinator can restore an assessment, request or cancel attached evidence recovery, or close a unit without clearing history.",
12
- freeflow_project:
19
+ select_evidence:
13
20
  "Inspect current selection and scoped candidates when needed, then add or remove exposed worker task-evidence refs. Strict shapes: inspect uses operation plus optional scope/cursor; add uses operation plus eligible refs only; remove uses operation plus currently selected or unresolved refs plus required reason. Reason is remove-only. Only tool results are evidence; put anything you would say in the report.",
14
21
  };
15
22
  const guidance = {
16
- freeflow_delegate: [
17
- "freeflow_delegate is Coordinator-only under Automatic control. Put the contract in the input. Choose worker when both Helper and Executor are enabled; the sole worker is inferred otherwise. Use replace only for explicit same-unit replacement; do not supply IDs.",
23
+ delegate_work: [
24
+ "delegate_work is Coordinator-only under Automatic control. Put the contract in the input. Choose worker when both Helper and Executor are enabled; the sole worker is inferred otherwise. Use replace only for explicit same-unit replacement; do not supply IDs.",
18
25
  ],
19
- freeflow_return: [
20
- "freeflow_return submit saves/revises the assignment report; supplement saves separate recovery communication; retry completes the current unchanged saved return without text. Stop ordinary task or recovery-read work after its communication is saved. Keep the handoff last; do not batch it with new task tools.",
26
+ return_work: [
27
+ "return_work submit saves/revises the assignment report; supplement saves separate recovery communication; retry completes the current unchanged saved return without text. Stop ordinary task or recovery-read work after its communication is saved. Keep the handoff last; do not batch it with new task tools.",
21
28
  ],
22
- freeflow_unit: [
23
- "freeflow_unit inspect defaults to current state; view history returns work refs and detail reads saved communication. Coordinator recover requests bounded evidence work against the current returned assessment; cancel-recovery ends only that recovery; assess restores a suspended evidence obligation; close changes work status without clearing communication or completing a Working Record task.",
29
+ routing_unit: [
30
+ "routing_unit inspect defaults to current state; view history returns work refs and detail reads saved communication. Coordinator recover requests bounded evidence work against the current returned assessment; cancel-recovery ends only that recovery; assess restores a suspended evidence obligation; close changes work status without clearing communication or completing a Working Record task.",
24
31
  ],
25
- freeflow_project: [
32
+ select_evidence: [
26
33
  "Use exactly one shape: inspect {operation, scope?, cursor?}; add {operation, refs} with no reason; remove {operation, refs, reason}. Reason is required only for remove and invalid for add/inspect. Add exact eligible visible refs directly when identity and eligibility are clear; inspect when identity, eligibility, representation or selection state is unclear. Never add refs marked not offered for new evidence selection. For remove, use currently selected or unresolved refs; a non-selectable ref may be named only to clear its unresolved request and requires a reason. Only tool results are evidence: select the result bodies behind your claims, not calls or your own messages, and say everything else in the report. Correct or explicitly withdraw unresolved items with a limitation. Known previously exposed bodies resolve automatically; target-representation gaps are not successful delivery.",
27
34
  ],
28
35
  };
@@ -30,11 +37,14 @@ export function registerRoutingTools(pi, runtime) {
30
37
  for (const name of ROUTING_TOOLS) {
31
38
  const definition = {
32
39
  name,
33
- label: name.replace("freeflow_", "Routing "),
40
+ label: ROUTING_LABELS[name],
34
41
  description: descriptions[name],
35
42
  parameters: ROUTING_SCHEMAS[name],
36
43
  // Handoffs and evidence selection are assistant-issued boundaries: codemode scripts must not call them.
37
44
  exposure: "model-only",
45
+ // Declared only while routing is effective: hidden until applyRoutingToolVisibility adds them, like Freeflow's
46
+ // other tools, so no loadout before Freeflow's first update carries them (Pi 1.4.0 activates tools by default).
47
+ defaultActive: false,
38
48
  executionMode: "sequential",
39
49
  promptGuidelines: guidance[name],
40
50
  renderCall: (args, _theme, context) => renderRoutingCall(name, args, context),
@@ -47,14 +57,18 @@ export function registerRoutingTools(pi, runtime) {
47
57
  pi.registerTool(definition);
48
58
  }
49
59
  }
50
- export function applyRoutingToolVisibility(pi, _runtime, _available = true) {
60
+ export function applyRoutingToolVisibility(pi, _runtime, available = true) {
51
61
  if (!pi.getActiveTools || !pi.setActiveTools) return;
52
62
  const current = pi.getActiveTools();
63
+ // Declared only while routing is effective (Task 003 D-002); turning it on or off costs one cache write (D-001).
53
64
  // Missing routing tools are appended and present ones keep their place, so tools another extension adds later
54
65
  // (tool_search, for one) are never reordered around them.
55
66
  const kept = current.filter(
56
- (name) => name !== "freeflow_switch_profile" && name !== "freeflow_cognitive_routing_history",
67
+ (name) =>
68
+ name !== "freeflow_switch_profile" &&
69
+ name !== "freeflow_cognitive_routing_history" &&
70
+ (available || !ROUTING_TOOLS.includes(name)),
57
71
  );
58
- const next = [...kept, ...ROUTING_TOOLS.filter((name) => !kept.includes(name))];
72
+ const next = available ? [...kept, ...ROUTING_TOOLS.filter((name) => !kept.includes(name))] : kept;
59
73
  if (JSON.stringify(next) !== JSON.stringify(current)) pi.setActiveTools(next);
60
74
  }
@@ -39,4 +39,13 @@ export function idFor(ref) {
39
39
  return /^ctx:([^\s:#]+)$/.exec(ref)?.[1];
40
40
  }
41
41
  export { canonical };
42
- export const ROUTING_TOOLS = ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"];
42
+ export const ROUTING_TOOLS = ["delegate_work", "return_work", "routing_unit", "select_evidence"];
43
+ /** The routing tools' names before Task 003's rename; sessions saved earlier still hold them in their history. */
44
+ export const LEGACY_ROUTING_TOOLS = {
45
+ freeflow_delegate: "delegate_work",
46
+ freeflow_return: "return_work",
47
+ freeflow_unit: "routing_unit",
48
+ freeflow_project: "select_evidence",
49
+ };
50
+ /** A routing tool's current name, for a name read from saved history. */
51
+ export const routingToolName = (name) => LEGACY_ROUTING_TOOLS[name] ?? name;
@@ -69,14 +69,14 @@ const text = (content) =>
69
69
  ? content.map((part) => (part?.type === "text" ? part.text : "")).join("")
70
70
  : "";
71
71
  /**
72
- * The latest user messages on the branch, oldest first, within the carried limit. Messages `skip` matches are left
73
- * out: Freeflow's own /freeflow compact request is sent as the user's message, and the compaction completes it.
72
+ * The latest user messages on the branch, oldest first, within the carried limit. Freeflow's own /freeflow compact
73
+ * request is a custom message, not a user message, so it is never among them.
74
74
  */
75
- export function latestUserMessages(branch, skip = () => false) {
75
+ export function latestUserMessages(branch) {
76
76
  const messages = branch
77
77
  .filter((entry) => entry?.type === "message" && entry.message?.role === "user")
78
78
  .map((entry) => text(entry.message.content).trim())
79
- .filter((message) => message && !skip(message))
79
+ .filter(Boolean)
80
80
  .slice(-USER_MESSAGE_LIMIT.count);
81
81
  const kept = [];
82
82
  let left = USER_MESSAGE_LIMIT.tokens;
@@ -90,28 +90,43 @@ export function latestUserMessages(branch, skip = () => false) {
90
90
  }
91
91
  /**
92
92
  * Text with each line's number in front, as `nl -ba` prints it, so a copy can be cited by file line without being read
93
- * again. Pi's note after a partial read ("[Showing lines …]", "[N more lines in file …]") stays unnumbered.
93
+ * again. A final newline ends the last line rather than starting another. Pi's note after a partial read ("[Showing
94
+ * lines …]", "[N more lines in file …]") stays unnumbered; a whole file has no such note (`readNote: false`).
94
95
  */
95
- export function numberLines(text, first) {
96
- const note = /\n\n\[(?:Showing lines \d+-\d+ of \d+|\d+ more lines in file)[^\n]*\]$/.exec(text);
97
- const body = note ? text.slice(0, note.index) : text;
98
- const lines = body.split("\n");
96
+ export function numberLines(text, first, { readNote = true } = {}) {
97
+ const note = readNote ? /\n\n\[(?:Showing lines \d+-\d+ of \d+|\d+ more lines in file)[^\n]*\]$/.exec(text) : null;
98
+ const whole = note ? text.slice(0, note.index) : text;
99
+ const ending = whole.endsWith("\n") ? "\n" : "";
100
+ const lines = (ending ? whole.slice(0, -1) : whole).split("\n");
99
101
  const width = String(first + lines.length - 1).length;
100
- return lines.map((line, i) => `${String(first + i).padStart(width)}\t${line}`).join("\n") + (note ? note[0] : "");
102
+ const numbered = lines.map((line, i) => `${String(first + i).padStart(width)}\t${line}`).join("\n");
103
+ return numbered + ending + (note ? note[0] : "");
101
104
  }
102
105
  function fenced(body) {
103
106
  const longest = Math.max(2, ...[...body.matchAll(/`+/g)].map((match) => match[0].length));
104
107
  const fence = "`".repeat(longest + 1);
105
108
  return `${fence}\n${body}\n${fence}`;
106
109
  }
107
- export function renderCarried(noticePrefix, cycle, userMessages, files, results = [], options = {}) {
110
+ /**
111
+ * Copied text cannot open or close a `<user-message>` tag, so nothing but the user's own messages reads as their words.
112
+ */
113
+ function withoutUserMessageTags(text) {
114
+ return text.replace(/<(\/?)(user-message)/gi, "<\\$1$2");
115
+ }
116
+ /**
117
+ * One of the user's latest messages. Freeflow's guidance says text in a notice is not the user's instruction, except what
118
+ * these tags quote.
119
+ */
120
+ function userMessage(text, index) {
121
+ return `<user-message index="${index}">\n${withoutUserMessageTags(text)}\n</user-message>`;
122
+ }
123
+ export function renderCarried(notice, cycle, userMessages, files, results = [], options = {}) {
108
124
  const parts = [
109
- `# Carried context\n\n${noticePrefix} Freeflow carried this into cycle ${cycle} at compaction. Only the latest user messages are the user's words, and they take precedence over older instructions in the summary or a Working Record; the rest is copied content.`,
125
+ `# Carried context\n\nFreeflow carried this into cycle ${cycle} at compaction. Only the latest user messages, in \`<user-message>\` tags, are the user's words, and they take precedence over older instructions in the summary or a Working Record; the rest is copied content.`,
110
126
  ];
111
127
  if (userMessages.length)
112
128
  parts.push(
113
- "## Latest user messages\n\n" +
114
- userMessages.map((message, i) => `### Message ${i + 1}\n\n${message}`).join("\n\n"),
129
+ "## Latest user messages\n\n" + userMessages.map((message, i) => userMessage(message, i + 1)).join("\n\n"),
115
130
  );
116
131
  if (files.length)
117
132
  parts.push(
@@ -121,7 +136,7 @@ export function renderCarried(noticePrefix, cycle, userMessages, files, results
121
136
  const range = "lines" in file && file.lines ? ` (lines ${file.lines[0]}-${file.lines[1]})` : "";
122
137
  return "error" in file
123
138
  ? `### ${file.path}\n\nNot carried: ${file.error}.`
124
- : `### ${file.path}${range}\n\n${fenced(numberLines(file.text, "lines" in file && file.lines ? file.lines[0] : 1))}`;
139
+ : `### ${file.path}${range}\n\n${fenced(numberLines(withoutUserMessageTags(file.text), "lines" in file && file.lines ? file.lines[0] : 1, { readNote: false }))}`;
125
140
  })
126
141
  .join("\n\n"),
127
142
  );
@@ -131,26 +146,25 @@ export function renderCarried(noticePrefix, cycle, userMessages, files, results
131
146
  results
132
147
  .map(
133
148
  (result) =>
134
- `### ${result.id} ${result.tool}${result.label && result.label !== result.id ? `: ${result.label}` : ""}\n\n${fenced(result.firstLine === undefined ? result.text : numberLines(result.text, result.firstLine))}`,
149
+ `### ${result.id} ${result.tool}${result.label && result.label !== result.id ? `: ${result.label}` : ""}\n\n${fenced(result.firstLine === undefined ? withoutUserMessageTags(result.text) : numberLines(withoutUserMessageTags(result.text), result.firstLine))}`,
135
150
  )
136
151
  .join("\n\n"),
137
152
  );
138
153
  const listed = renderListed(options.listed ?? [], options.scope ?? "cycle", "Other results");
139
154
  if (listed) parts.push(listed);
140
- return parts.join("\n\n");
155
+ return notice(parts.join("\n\n"));
141
156
  }
142
157
  /**
143
158
  * The carried context as the Coordinator sees it under projection: the user's latest messages, and only the names of
144
159
  * the worker's copies. The Coordinator receives evidence by selection; the copies would repeat it and enlarge its view.
145
160
  */
146
- export function renderCarriedForCoordinator(noticePrefix, cycle, userMessages, files, results) {
161
+ export function renderCarriedForCoordinator(notice, cycle, userMessages, files, results) {
147
162
  const parts = [
148
- `# Carried context\n\n${noticePrefix} Freeflow carried this into cycle ${cycle} at the worker's compaction. Only the latest user messages are the user's words, and they take precedence over older instructions in the summary or a Working Record.`,
163
+ `# Carried context\n\nFreeflow carried this into cycle ${cycle} at the worker's compaction. Only the latest user messages, in \`<user-message>\` tags, are the user's words, and they take precedence over older instructions in the summary or a Working Record.`,
149
164
  ];
150
165
  if (userMessages.length)
151
166
  parts.push(
152
- "## Latest user messages\n\n" +
153
- userMessages.map((message, i) => `### Message ${i + 1}\n\n${message}`).join("\n\n"),
167
+ "## Latest user messages\n\n" + userMessages.map((message, i) => userMessage(message, i + 1)).join("\n\n"),
154
168
  );
155
169
  const names = [
156
170
  ...files
@@ -168,7 +182,7 @@ export function renderCarriedForCoordinator(noticePrefix, cycle, userMessages, f
168
182
  "## Carried for the worker\n\nThe worker's copies are left out of this view; evidence it selects reaches you as usual.\n\n" +
169
183
  names.join("\n"),
170
184
  );
171
- return parts.join("\n\n");
185
+ return notice(parts.join("\n\n"));
172
186
  }
173
187
  /** Results of the work in progress named by ref, so the agent knows they exist without their content in context. */
174
188
  export function renderListed(listed, scope, heading, intro) {
@@ -1,14 +1,16 @@
1
1
  /** Freeflow compaction is on whenever Freeflow is, unless a config turns it off. */
2
2
  export const DEFAULT_COMPACTION_ENABLED = true;
3
3
  export const DEFAULT_COMPACTION_CARRY = true;
4
+ /** The keys `compaction` accepts; the settings test checks each has a /freeflow settings row. */
5
+ export const COMPACTION_KEYS = ["enabled", "carry"];
4
6
  function record(value) {
5
7
  return typeof value === "object" && value !== null && !Array.isArray(value);
6
8
  }
7
9
  export function validateCompactionConfig(value) {
8
10
  if (!record(value)) return "compaction must be an object";
9
- const unknown = Object.keys(value).find((key) => key !== "enabled" && key !== "carry");
11
+ const unknown = Object.keys(value).find((key) => !COMPACTION_KEYS.includes(key));
10
12
  if (unknown) return `compaction has unsupported key: ${unknown}`;
11
- for (const key of ["enabled", "carry"])
13
+ for (const key of COMPACTION_KEYS)
12
14
  if (value[key] !== undefined && typeof value[key] !== "boolean") return `compaction.${key} must be a boolean`;
13
15
  return null;
14
16
  }
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { resolveToolPath } from "../tool-execution/file-state.js";
3
+ import { skillsRead } from "../tool-execution/skill-reads.js";
3
4
  import {
4
5
  carryBudget,
5
6
  carryProblem,
@@ -12,7 +13,7 @@ import {
12
13
  renderListed,
13
14
  SUMMARY_LIMIT_TOKENS,
14
15
  } from "./carry.js";
15
- import { cycleFiles, harnessPart, nextCycle, previousFileLists, sessionFileLists } from "./harness.js";
16
+ import { currentCycle, cycleFiles, harnessPart, nextCycle, previousFileLists, sessionFileLists } from "./harness.js";
16
17
  import {
17
18
  INDEX_TOKEN_BOUND,
18
19
  renderIndex,
@@ -25,16 +26,15 @@ import { cachedBranch } from "../host/branch.js";
25
26
  /**
26
27
  * Freeflow compaction's agent path. After each turn Freeflow measures the context and, once per cycle each, says
27
28
  * compaction is due (the warning, with the result index) and then to compact now. The agent asks to compact with
28
- * freeflow_compact once compaction is due. At the end of that turn Freeflow writes Pi's compaction entry, keeping
29
+ * compact_context once compaction is due. At the end of that turn Freeflow writes Pi's compaction entry, keeping
29
30
  * nothing raw, followed by the carried context and a recovery message, and asks Pi to continue the same run. Pi fires
30
31
  * no compaction events for this, so the caller runs Freeflow's post-compaction resets before the next request
31
32
  * (takeCompacted).
32
33
  */
33
34
  export const CARRIED_TYPE = "freeflow-carried-context";
34
35
  export const RECOVERY_TYPE = "freeflow-compaction-recovery";
35
- /** What /freeflow compact sends as the user's message; a list of results may follow it. */
36
- export const USER_REQUEST =
37
- "Compact now: read the compaction skill, prepare the next cycle, and call freeflow_compact.";
36
+ /** What /freeflow compact asks for, in a Freeflow notice; a list of results may follow it. */
37
+ export const USER_REQUEST = "Compact now: read the compaction skill, prepare the next cycle, and call compact_context.";
38
38
  const ABNORMAL_STOPS = new Set(["error", "aborted", "length"]);
39
39
  /** The most results the carried context lists by ref; the newest are listed. */
40
40
  const MAX_LISTED = 60;
@@ -145,27 +145,30 @@ export class CompactionController {
145
145
  if (level === "compactNow") this.sent.compactNowAt = tokens;
146
146
  return { level, text: this.noticeText(level, tokens, thresholds, ctx, withList) };
147
147
  }
148
+ /** A compaction notice, tagged as Freeflow's (the host's notice()). */
148
149
  noticeText(level, tokens, thresholds, ctx, withList = true) {
149
- const prefix = this.host.noticePrefix;
150
+ return this.host.notice(this.noticeTextBody(level, tokens, thresholds, ctx, withList));
151
+ }
152
+ noticeTextBody(level, tokens, thresholds, ctx, withList = true) {
150
153
  const { window, trigger } = thresholds;
151
154
  if (this.host.coordinatorUnderProjection())
152
- return `${prefix} ${level === "compactNow" ? "Compact now" : "Compaction is due soon"}: the full history is about ${tokens} of ${window} tokens, and Pi compacts on its own past about ${trigger}. Your view leaves out what workers did, so do not compact yourself: ${level === "compactNow" ? "now" : "at a safe point (after the current assessment, or before the next assignment)"}, delegate an assignment asking the worker to read the compaction skill, compact with freeflow_compact, and then return with freeflow_return; then continue.`;
155
+ return `${level === "compactNow" ? "Compact now" : "Compaction is due soon"}: the full history is about ${tokens} of ${window} tokens, and Pi compacts on its own past about ${trigger}${level === "compactNow" ? ", or when a request overflows the model's window" : ""}. Your view leaves out what workers did, so do not compact yourself: ${level === "compactNow" ? "now" : "at a safe point (after the current assessment, or before the next assignment)"}, delegate an assignment asking the worker to read the compaction skill, compact with compact_context, and then return with return_work; then continue.`;
153
156
  const index = withList ? this.indexText(ctx) : "";
154
157
  const list = index ? `\n\n${index}` : "";
155
158
  // A worker's return hands the same history to the Coordinator and the next assignment, so it ends no growth.
156
159
  const worker = this.host.routingAssignment() !== undefined;
157
160
  if (level === "compactNow")
158
- return `${prefix} Compact now: context is at about ${tokens} of ${window} tokens. Pi compacts on its own past about ${trigger}, or when a request overflows the model's window. The end of your current step is the safe point: write down any partial state, then prepare and call freeflow_compact; ${worker ? "compact before you return, since the Coordinator and the next assignment continue this history; " : ""}if Pi compacts first, its own summary replaces yours.${list}`;
161
+ return `Compact now: context is at about ${tokens} of ${window} tokens. Pi compacts on its own past about ${trigger}, or when a request overflows the model's window. The end of your current step is the safe point: write down any partial state, then prepare and call compact_context; ${worker ? "compact before you return, since the Coordinator and the next assignment continue this history; " : ""}if Pi compacts first, its own summary replaces yours.${list}`;
159
162
  // Before compact now there is room for the Coordinator's next turn, so a worker may finish a return first.
160
163
  const end = `If you are at one now, compact now; if your work ends within a step or two with ${worker ? "a return" : "a final answer"}, finish it instead.`;
161
- return `${prefix} Compaction is due soon: context is at about ${tokens} of ${window} tokens, and Pi compacts on its own past about ${trigger}. Keep working until a safe point: a finished step whose result you know (a check read, an edit set complete, a report written), not mid-edit or with a command running. ${end} At the safe point, read the compaction skill, update the Working Record, ${this.carryText(ctx)}, write the summary, and call freeflow_compact. A "compact now" notice follows near Pi's limit.${list}`;
164
+ return `Compaction is due soon: context is at about ${tokens} of ${window} tokens, and Pi compacts on its own past about ${trigger}. Keep working until a safe point: a finished step whose result you know (a check read, an edit set complete, a report written), not mid-edit or with a command running. ${end} At the safe point, read the compaction skill, update the Working Record, ${this.carryText(ctx)}, write the summary, and call compact_context. A "compact now" notice follows near Pi's limit.${list}`;
162
165
  }
163
- /** Check a freeflow_compact call and schedule it for the end of this turn; throws with the reason it is refused. */
166
+ /** Check a compact_context call and schedule it for the end of this turn; throws with the reason it is refused. */
164
167
  async request(params, ctx) {
165
168
  if (!this.host.effective()) throw new Error("Freeflow compaction is turned off.");
166
169
  if (this.host.coordinatorUnderProjection())
167
170
  throw new Error(
168
- "Your view leaves out what workers did, so do not compact yourself: delegate an assignment asking the worker to read the compaction skill, compact with freeflow_compact, and then return with freeflow_return.",
171
+ "Your view leaves out what workers did, so do not compact yourself: delegate an assignment asking the worker to read the compaction skill, compact with compact_context, and then return with return_work.",
169
172
  );
170
173
  if (!this.isDue(ctx)) throw new Error(notDueText(branchOf(ctx)));
171
174
  const summary = typeof params?.summary === "string" ? params.summary.trim() : "";
@@ -173,13 +176,13 @@ export class CompactionController {
173
176
  const summaryTokens = estimateTokens(summary);
174
177
  if (summaryTokens > SUMMARY_LIMIT_TOKENS)
175
178
  throw new Error(
176
- `The summary is about ${summaryTokens} tokens; the limit is ${SUMMARY_LIMIT_TOKENS}. Shorten it by about ${summaryTokens - SUMMARY_LIMIT_TOKENS} tokens and call freeflow_compact again.`,
179
+ `The summary is about ${summaryTokens} tokens; the limit is ${SUMMARY_LIMIT_TOKENS}. Shorten it by about ${summaryTokens - SUMMARY_LIMIT_TOKENS} tokens and call compact_context again.`,
177
180
  );
178
181
  const carry = params?.carry === undefined ? [] : params.carry;
179
182
  if (!Array.isArray(carry)) throw new Error("carry must be a list of { file, lines? } or { result } items.");
180
183
  if (carry.length && !this.host.carryEnabled())
181
184
  throw new Error(
182
- "Context reuse is off: call freeflow_compact without carry. The user's latest messages are carried for you.",
185
+ "Context reuse is off: call compact_context without carry. The user's latest messages are carried for you.",
183
186
  );
184
187
  const problems = carry.map(carryProblem).filter(Boolean);
185
188
  if (problems.length) throw new Error(`Fix the carry list: ${problems.join("; ")}.`);
@@ -299,15 +302,25 @@ export class CompactionController {
299
302
  requestedBy,
300
303
  routingProfile: this.host.routingProfile(),
301
304
  background: this.host.background(),
305
+ skills: skillsRead(currentCycle(branch), ctx.cwd),
302
306
  files: sessionFileLists(branch),
303
307
  })}`;
304
- const userMessages = latestUserMessages(branch, (message) => message.startsWith(USER_REQUEST));
308
+ const userMessages = latestUserMessages(branch);
305
309
  const { automatic, listed } = this.fill(branch, ctx, files, results);
306
- const carriedText = renderCarried(this.host.noticePrefix, cycle, userMessages, files, [...results, ...automatic], {
307
- listed,
308
- scope: this.host.nativeRefs() && this.host.assignmentResults() ? "assignment" : "cycle",
309
- });
310
- const recovery = `${this.host.noticePrefix} ${recoveryText(cycle, "The carried context is above: its files were read at compaction, so do not read them or its results again unless something has changed them since. ")} ${AFTER_REQUESTED}`;
310
+ const carriedText = renderCarried(
311
+ (text) => this.host.notice(text),
312
+ cycle,
313
+ userMessages,
314
+ files,
315
+ [...results, ...automatic],
316
+ {
317
+ listed,
318
+ scope: this.host.nativeRefs() && this.host.assignmentResults() ? "assignment" : "cycle",
319
+ },
320
+ );
321
+ const recovery = this.host.notice(
322
+ `${recoveryText(cycle, "The files and results copied into the carried context above were read at compaction: do not read those again unless something has changed them. Skills, and anything listed there only by name, were not carried: read them when you need them. ")} ${AFTER_REQUESTED}`,
323
+ );
311
324
  this.requested = undefined;
312
325
  this.compacted = { carriedFiles: files.filter((file) => "text" in file).map((file) => file.path) };
313
326
  return {
@@ -343,10 +356,13 @@ export class CompactionController {
343
356
  ...(this.host.nativeRefs()
344
357
  ? {
345
358
  details: {
346
- coordinatorContent: renderCarriedForCoordinator(this.host.noticePrefix, cycle, userMessages, files, [
347
- ...results,
348
- ...automatic,
349
- ]),
359
+ coordinatorContent: renderCarriedForCoordinator(
360
+ (text) => this.host.notice(text),
361
+ cycle,
362
+ userMessages,
363
+ files,
364
+ [...results, ...automatic],
365
+ ),
350
366
  },
351
367
  }
352
368
  : {}),
@@ -382,6 +398,7 @@ export class CompactionController {
382
398
  cycle,
383
399
  routingProfile: this.host.routingProfile(),
384
400
  background: this.host.background(),
401
+ skills: skillsRead(currentCycle(branch), ctx.cwd),
385
402
  files: { readFiles: [], modifiedFiles: [] },
386
403
  includeFiles: false,
387
404
  listed: this.assignmentList(branch),
@@ -410,8 +427,14 @@ export class CompactionController {
410
427
  return compacted;
411
428
  }
412
429
  }
413
- /** Freeflow's additions to Pi's own summarizer, written for a reader that summarizes someone else's conversation. */
430
+ /**
431
+ * Freeflow's additions to Pi's own summarizer, written for a reader that summarizes someone else's conversation. From
432
+ * Pi 1.4.0 the summarizer's output is no longer capped at 80% of Pi's reserve but at the model's output limit (reduced
433
+ * to what the window leaves), so the length is asked for: the next cycle then starts within the room an agent's
434
+ * compaction leaves, whose summary Freeflow caps at the same limit.
435
+ */
414
436
  const FALLBACK_INSTRUCTIONS = [
437
+ `Keep the summary under ${SUMMARY_LIMIT_TOKENS.toLocaleString("en-US")} tokens; the next part of the conversation starts from it.`,
415
438
  "Write state, not a story: what is true now, each item marked as settled, tentative, superseded, or unverified.",
416
439
  "Keep exact paths, identifiers, commands, error text, numbers, and the user's words where wording matters; never paraphrase a decision or a constraint.",
417
440
  "Never record a requested, reported, or expected result as observed. Name partial or uncommitted changes, unverified results, running commands, and obligations with what triggers each.",
@@ -423,18 +446,18 @@ const FALLBACK_INSTRUCTIONS = [
423
446
  * pointing at the wrong one sends recovery into another task. The summary names the record when there is one.
424
447
  */
425
448
  function recoveryText(cycle, carried = "") {
426
- return `Compaction finished; cycle ${cycle} starts here. Recover before you continue: if the summary names a Working Record for this work, recover it as Track Work says; otherwise the summary is your record for this cycle, so re-read its Recovery sources. Reconcile with the live state your next step depends on, and verify work the summary calls done instead of redoing it. ${carried}This compaction is done, so do not re-read the compaction skill or its summary format. Then continue the work the compaction interrupted.`;
449
+ return `Compaction finished; cycle ${cycle} starts here. Recover before you continue: if the summary names a Working Record for this work, recover it as Track Work says; otherwise the summary is your record for this cycle, so re-read its Recovery sources. Reconcile with the live state your next step depends on, and verify work the summary calls done instead of redoing it. ${carried}Skills you read before this point are out of view, even where the summary lists their files: read each one again before you apply its method, the cognitive-routing skill first when routing is on. This compaction is done, so do not re-read the compaction skill or its summary format. Then continue the work the compaction interrupted.`;
427
450
  }
428
451
  /** The agent path: the compaction was the request, and it is done. */
429
452
  const AFTER_REQUESTED =
430
- "This compaction completes the request for it: do not call freeflow_compact again in this cycle. If compacting was all you were asked to do, report that it is done (a worker returns its assignment with freeflow_return).";
453
+ "This compaction completes the request for it: do not call compact_context again in this cycle. If compacting was all you were asked to do, report that it is done (a worker returns its assignment with return_work).";
431
454
  /** Pi compacted on its own, so a Freeflow compaction being prepared is no longer needed. */
432
- const AFTER_PI = "Compaction is no longer due: do not call freeflow_compact.";
433
- /** Why freeflow_compact is refused when compaction is not due, and where to go next if a compaction began this cycle. */
455
+ const AFTER_PI = "Compaction is no longer due: do not call compact_context.";
456
+ /** Why compact_context is refused when compaction is not due, and where to go next if a compaction began this cycle. */
434
457
  function notDueText(branch) {
435
458
  const latest = [...branch].reverse().find((entry) => entry?.type === "compaction");
436
459
  const next =
437
- "Continue the work; if this is a routing assignment that only asked for compaction, return it with freeflow_return.";
460
+ "Continue the work; if this is a routing assignment that only asked for compaction, return it with return_work.";
438
461
  if (!latest)
439
462
  return "Compaction is not due. Freeflow says when it is; the user can also ask for it with /freeflow compact.";
440
463
  if (latest.details?.freeflow && !latest.details.freeflow.fallback)
@@ -7,11 +7,26 @@ export function currentCycle(branch) {
7
7
  });
8
8
  return branch.slice(start);
9
9
  }
10
+ /**
11
+ * The entries whose file touches no compaction has listed yet: the current cycle, plus the latest compaction's kept
12
+ * tail. Pi keeps raw the entries from its `firstKeptEntryId`, which sit before its compaction entry on the branch, and
13
+ * lists only what it summarized; Freeflow's compactions keep nothing raw.
14
+ */
15
+ function unlistedEntries(branch) {
16
+ let latest = branch.length - 1;
17
+ while (latest >= 0 && branch[latest]?.type !== "compaction") latest--;
18
+ if (latest < 0) return [...branch];
19
+ const keptFrom = branch[latest].firstKeptEntryId
20
+ ? branch.findIndex((entry, i) => i < latest && entry?.id === branch[latest].firstKeptEntryId)
21
+ : -1;
22
+ const tail = keptFrom < 0 ? [] : branch.slice(keptFrom, latest).filter((entry) => entry?.type !== "compaction");
23
+ return [...tail, ...branch.slice(latest + 1)];
24
+ }
10
25
  /** The cycle number the next compaction starts. Cycle 1 runs from session start to the first compaction. */
11
26
  export const nextCycle = (branch) => branch.filter((entry) => entry?.type === "compaction").length + 2;
12
27
  /**
13
- * Files successful calls in this cycle read or changed, in first-touched order. Calls a codemode script made count
14
- * too: Pi records them on the script's result as nested calls.
28
+ * Files successful calls read or changed since the latest compaction listed them (this cycle, and Pi's kept tail), in
29
+ * first-touched order. Calls a codemode script made count too: Pi records them on the script's result as nested calls.
15
30
  */
16
31
  export function cycleFiles(branch) {
17
32
  const calls = new Map();
@@ -20,7 +35,7 @@ export function cycleFiles(branch) {
20
35
  const touch = (name, input) => {
21
36
  for (const path of touchedPaths(name, input)) (name === "read" ? read : changed).add(path);
22
37
  };
23
- for (const entry of currentCycle(branch)) {
38
+ for (const entry of unlistedEntries(branch)) {
24
39
  const message = entry?.type === "message" ? entry.message : undefined;
25
40
  if (message?.role === "assistant")
26
41
  for (const block of message.content ?? [])
@@ -69,6 +84,7 @@ export function harnessPart(facts) {
69
84
  lines.push(
70
85
  `Background commands still running: ${facts.background.map((job) => `${job.id} (${job.label}), output ${job.outputPath}`).join("; ")}.`,
71
86
  );
87
+ if (facts.skills?.length) lines.push(`Freeflow skills read in cycle ${facts.cycle - 1}: ${facts.skills.join(", ")}.`);
72
88
  const files = facts.includeFiles === false ? "" : formatFileLists(facts.files);
73
89
  if (files) lines.push("", "Files read and modified in this session:", "", files);
74
90
  if (facts.listed) lines.push("", facts.listed.replace(/^## /, "### "));
@@ -1,7 +1,7 @@
1
- import { resolve, sep } from "node:path";
2
- import { fileURLToPath } from "node:url";
3
1
  import { estimateTokens } from "./carry.js";
4
2
  import { currentCycle } from "./harness.js";
3
+ import { withoutGuidanceNotices } from "../tool-execution/guidance-notices.js";
4
+ import { isFreeflowInstruction } from "../tool-execution/skill-reads.js";
5
5
  /**
6
6
  * The result index: this cycle's larger tool results, each with an id the agent can carry by. Ids number tool results
7
7
  * in branch order (r1, r2, ...), so they stay stable while the branch grows, and a result from an earlier cycle still
@@ -16,16 +16,25 @@ const MAX_ROWS = 40;
16
16
  export const INDEX_TOKEN_BOUND = 2_000;
17
17
  /** Freeflow's own control calls: no evidence to carry, as routing's evidence selection also excludes them. */
18
18
  const CONTROL_TOOLS = new Set([
19
+ "delegate_work",
20
+ "return_work",
21
+ "select_evidence",
22
+ "routing_unit",
23
+ "compact_context",
24
+ // The same tools' names before Task 003's rename, still in sessions saved earlier.
19
25
  "freeflow_delegate",
20
26
  "freeflow_return",
21
27
  "freeflow_project",
22
28
  "freeflow_unit",
23
29
  "freeflow_compact",
24
30
  ]);
31
+ /** A result's text without the guidance notices that joined it. */
25
32
  const resultText = (message) =>
26
- (message?.content ?? [])
27
- .map((part) => (part?.type === "text" ? part.text : part?.type === "image" ? "[image]" : ""))
28
- .join("");
33
+ withoutGuidanceNotices(
34
+ (message?.content ?? [])
35
+ .map((part) => (part?.type === "text" ? part.text : part?.type === "image" ? "[image]" : ""))
36
+ .join(""),
37
+ );
29
38
  function label(name, input) {
30
39
  const firstLine = (text) =>
31
40
  text
@@ -49,14 +58,6 @@ function label(name, input) {
49
58
  const line = firstLine(String(value));
50
59
  return line.length > 80 ? `${line.slice(0, 77)}...` : line;
51
60
  }
52
- /** The Freeflow package root: reads of its skills, references and prompts are instructions, not work to carry. */
53
- const FREEFLOW_ROOT = fileURLToPath(new URL("../../../", import.meta.url));
54
- const INSTRUCTION_DIRS = ["capabilities", "skills", "runtime"].map((dir) => resolve(FREEFLOW_ROOT, dir) + sep);
55
- function isFreeflowInstruction(tool, input) {
56
- if (tool !== "read" || typeof input?.path !== "string") return false;
57
- const path = resolve(input.path);
58
- return INSTRUCTION_DIRS.some((dir) => path.startsWith(dir));
59
- }
60
61
  /** Every tool result on the branch with its id, oldest first. */
61
62
  function allResults(branch) {
62
63
  const calls = new Map();
@@ -1,10 +1,10 @@
1
- export const FREEFLOW_COMPACT = "freeflow_compact";
1
+ export const COMPACT_CONTEXT = "compact_context";
2
2
  const DESCRIPTION =
3
3
  "Compact the conversation into a new cycle. Use only when Freeflow says compaction is due or the user asks, and read the compaction skill first. Pass your summary and what to carry; compaction happens at the end of this turn, and the run continues from the summary, the carried context and a recovery message.";
4
4
  /** Registered once, declared only while Freeflow compaction is effective (see applyCompactionTools). */
5
5
  export function registerCompactionTool(pi, controller) {
6
6
  pi.registerTool({
7
- name: FREEFLOW_COMPACT,
7
+ name: COMPACT_CONTEXT,
8
8
  label: "Compact",
9
9
  description: DESCRIPTION,
10
10
  parameters: {
@@ -47,11 +47,11 @@ export function registerCompactionTool(pi, controller) {
47
47
  },
48
48
  });
49
49
  }
50
- /** Declare or withdraw freeflow_compact when compaction's effective state changes, keeping other tools in place. */
50
+ /** Declare or withdraw compact_context when compaction's effective state changes, keeping other tools in place. */
51
51
  export function applyCompactionTools(pi, effective) {
52
52
  if (typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return;
53
53
  const current = pi.getActiveTools();
54
- const present = current.includes(FREEFLOW_COMPACT);
55
- if (effective && !present) pi.setActiveTools([...current, FREEFLOW_COMPACT]);
56
- if (!effective && present) pi.setActiveTools(current.filter((name) => name !== FREEFLOW_COMPACT));
54
+ const present = current.includes(COMPACT_CONTEXT);
55
+ if (effective && !present) pi.setActiveTools([...current, COMPACT_CONTEXT]);
56
+ if (!effective && present) pi.setActiveTools(current.filter((name) => name !== COMPACT_CONTEXT));
57
57
  }