frizz-server 0.15.20 → 0.15.23

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 (112) hide show
  1. package/dist/claude-agent-broker.js +371 -39
  2. package/dist/dev-child.js +8255 -6173
  3. package/package.json +1 -1
  4. package/runtime/cc-worker/DECISIONS.md +42 -2
  5. package/runtime/cc-worker/bin/frizz-mcp.mjs +11 -1848
  6. package/runtime/cc-worker/bin/frizzctl +14 -0
  7. package/runtime/cc-worker/bin/frizzctl.cmd +9 -0
  8. package/runtime/cc-worker/bin/frizzctl.mjs +438 -0
  9. package/runtime/cc-worker/hooks/agent-dispatch.mjs +2 -2
  10. package/runtime/cc-worker/hooks/deny-ask.mjs +3 -3
  11. package/runtime/cc-worker/hooks/deny-plan.mjs +2 -2
  12. package/runtime/cc-worker/hooks/perm-policy.mjs +52 -1
  13. package/runtime/cc-worker/hooks/scratchpad.mjs +16 -10
  14. package/runtime/cc-worker/hooks/session-seed.mjs +1 -1
  15. package/runtime/cc-worker/lib/frizz-tools.mjs +2241 -0
  16. package/runtime/cc-worker/skills/gh/SKILL.md +6 -6
  17. package/web-dist/assets/{abnfDiagram-VRR7QNED-B61nQEjt.js → abnfDiagram-VRR7QNED-BW_CIF9J.js} +1 -1
  18. package/web-dist/assets/{arc-CR8_qWI3.js → arc-B475yv46.js} +1 -1
  19. package/web-dist/assets/architecture-TIHT7OUA-wAP7tOoN.js +1 -0
  20. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-6sc4SDOh.js → architectureDiagram-ZJ3FMSHR-DkPvFjFp.js} +1 -1
  21. package/web-dist/assets/{blockDiagram-677ZJIJ3-p-DPTDGB.js → blockDiagram-677ZJIJ3-Rls7Nttu.js} +1 -1
  22. package/web-dist/assets/{c4Diagram-LMCZKHZV-cqvAqZE2.js → c4Diagram-LMCZKHZV-maV0q2bv.js} +1 -1
  23. package/web-dist/assets/channel-Clg0HJPA.js +1 -0
  24. package/web-dist/assets/{chunk-32BRIVSS-BNnoKILR.js → chunk-32BRIVSS-Coi7L0de.js} +1 -1
  25. package/web-dist/assets/{chunk-52WLFC77-BmHIWiVu.js → chunk-52WLFC77-DZaDe-48.js} +1 -1
  26. package/web-dist/assets/{chunk-C7G6YPKG-D7XYFno8.js → chunk-C7G6YPKG-DNQ-JhD9.js} +1 -1
  27. package/web-dist/assets/{chunk-EX3LRPZG-BUViIha7.js → chunk-EX3LRPZG-BQCebwoA.js} +1 -1
  28. package/web-dist/assets/{chunk-FWX5IMBZ-3x5Zky3W.js → chunk-FWX5IMBZ-CqiHtpEB.js} +2 -2
  29. package/web-dist/assets/{chunk-HOUHSVGY-C6_RBSov.js → chunk-HOUHSVGY-Bop69PFg.js} +1 -1
  30. package/web-dist/assets/{chunk-ICXQ74PX-NbVxQ4i1.js → chunk-ICXQ74PX-D3PkeZ6B.js} +1 -1
  31. package/web-dist/assets/{chunk-MOJQB5TN-nRMfcSHe.js → chunk-MOJQB5TN-lidXw8-C.js} +1 -1
  32. package/web-dist/assets/{chunk-OGEWGWER-BbSXfbEB.js → chunk-OGEWGWER-CzD91FAh.js} +1 -1
  33. package/web-dist/assets/{chunk-PUDLZKDR-Byubn9VY.js → chunk-PUDLZKDR-Cilkzvck.js} +1 -1
  34. package/web-dist/assets/{chunk-Q4XR5HBZ-DLq-aZ3t.js → chunk-Q4XR5HBZ-C37UQCfb.js} +1 -1
  35. package/web-dist/assets/{chunk-RYQCIY6F-Dbt66M6V.js → chunk-RYQCIY6F-C0qnX-Ux.js} +1 -1
  36. package/web-dist/assets/{chunk-V7JOEXUC-DNFTmXz6.js → chunk-V7JOEXUC-Da5vOGHX.js} +1 -1
  37. package/web-dist/assets/{chunk-VAUOI2AC-QWrszv3X.js → chunk-VAUOI2AC-FrJadrJY.js} +1 -1
  38. package/web-dist/assets/{chunk-VR4S4FIN-CJdyjwam.js → chunk-VR4S4FIN-DYxK2Z50.js} +1 -1
  39. package/web-dist/assets/{chunk-WYO6CB5R-D_786t-3.js → chunk-WYO6CB5R-C164KTfT.js} +1 -1
  40. package/web-dist/assets/{chunk-XXDRQBXY-Bd_2jBj_.js → chunk-XXDRQBXY-D4wHtxet.js} +1 -1
  41. package/web-dist/assets/{chunk-ZGVPDNZ5-DklPG-LG.js → chunk-ZGVPDNZ5-BXKkVljT.js} +1 -1
  42. package/web-dist/assets/classDiagram-OUVF2IWQ-23bCXfvg.js +1 -0
  43. package/web-dist/assets/classDiagram-v2-EOCWNBFH-23bCXfvg.js +1 -0
  44. package/web-dist/assets/{cose-bilkent-JH36ORCC-C4MtSvEs.js → cose-bilkent-JH36ORCC-BFSgVzNT.js} +1 -1
  45. package/web-dist/assets/{cynefin-VYW2F7L2-Dr0CmMQz.js → cynefin-VYW2F7L2-BSoEbT-s.js} +1 -1
  46. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-BpHXKyDO.js → cynefinDiagram-TSTJHNR4-BBZgLbdX.js} +1 -1
  47. package/web-dist/assets/{dagre-VKFMJZFB-CWAtKn43.js → dagre-VKFMJZFB-Dx6giO7u.js} +1 -1
  48. package/web-dist/assets/{diagram-FQU43EPY-B_6PhpPG.js → diagram-FQU43EPY-BMI3qRca.js} +1 -1
  49. package/web-dist/assets/{diagram-G47NLZAW-DPNrX-3a.js → diagram-G47NLZAW-CJY-MA3L.js} +1 -1
  50. package/web-dist/assets/{diagram-NH7WQ7WH-BL4nqAUm.js → diagram-NH7WQ7WH-CbQVFJ3r.js} +1 -1
  51. package/web-dist/assets/{diagram-OA4YK3LP-CcFbtuCf.js → diagram-OA4YK3LP-CPnrakdY.js} +1 -1
  52. package/web-dist/assets/{diagram-WEI45ONY-Bkl0nXre.js → diagram-WEI45ONY-D9DNwzlo.js} +1 -1
  53. package/web-dist/assets/{dist-krVNNn4H.js → dist-CO93_xVa.js} +1 -1
  54. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-C_51YQqw.js → ebnfDiagram-CCIWWBDH-DNHudB8r.js} +1 -1
  55. package/web-dist/assets/{erDiagram-Q63AITRT-D8oBhiQB.js → erDiagram-Q63AITRT-Cwt63Xz8.js} +1 -1
  56. package/web-dist/assets/eventmodeling-45OFAUF4-Q3Nq1ecc.js +1 -0
  57. package/web-dist/assets/flowDiagram-23GEKE2U-B2snV3ot.js +1 -0
  58. package/web-dist/assets/{ganttDiagram-NO4QXBWP-BOJJvkcQ.js → ganttDiagram-NO4QXBWP-DiFpSJc2.js} +1 -1
  59. package/web-dist/assets/{gitGraph-TEB2WS4Q-DrZGs6X0.js → gitGraph-TEB2WS4Q-2-hTvDOu.js} +1 -1
  60. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-C0pqDVhD.js → gitGraphDiagram-IHSO6WYX-mbc4Il71.js} +1 -1
  61. package/web-dist/assets/index-C6AXnNpg.css +2 -0
  62. package/web-dist/assets/index-DewBdeY0.js +352 -0
  63. package/web-dist/assets/{info-DKCQHKI2-0OcIuVxc.js → info-DKCQHKI2-BO0ZUEnn.js} +1 -1
  64. package/web-dist/assets/{infoDiagram-FWYZ7A6U-CRAc-N7_.js → infoDiagram-FWYZ7A6U-Cn3DmnjS.js} +1 -1
  65. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-D_bHCCxh.js → ishikawaDiagram-FXEZZL3T-Ckwh42aC.js} +1 -1
  66. package/web-dist/assets/{journeyDiagram-5HDEW3XC-Dbvh7D1e.js → journeyDiagram-5HDEW3XC-D26ySLLD.js} +1 -1
  67. package/web-dist/assets/{kanban-definition-HUTT4EX6-CxOWhFpB.js → kanban-definition-HUTT4EX6-CXKwXGxe.js} +1 -1
  68. package/web-dist/assets/{line-BGqbhtvK.js → line-BHqhqZTw.js} +1 -1
  69. package/web-dist/assets/{linear-DRvKJ3WJ.js → linear-Xq8ICqCe.js} +1 -1
  70. package/web-dist/assets/{mermaid-parser.core-C5lIBam8.js → mermaid-parser.core-ByagDKoG.js} +3 -3
  71. package/web-dist/assets/{mermaid.core-ChLUqNEE.js → mermaid.core-CEwbkqrb.js} +3 -3
  72. package/web-dist/assets/{mindmap-definition-LN4V7U3C-BfPv_GOf.js → mindmap-definition-LN4V7U3C-C3S3nEo5.js} +1 -1
  73. package/web-dist/assets/{packet-7NZHBO7P-CH3B2i7_.js → packet-7NZHBO7P-BjcgqWf7.js} +1 -1
  74. package/web-dist/assets/{pegDiagram-2B236MQR-DLuCHUaK.js → pegDiagram-2B236MQR-Due7wPzY.js} +1 -1
  75. package/web-dist/assets/{pie-RZYD4A2V-C-hDlp0h.js → pie-RZYD4A2V-C1X-Fm2K.js} +1 -1
  76. package/web-dist/assets/{pieDiagram-ENE6RG2P-CKVu3PTC.js → pieDiagram-ENE6RG2P-CbKS5jFv.js} +1 -1
  77. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL--tZ3JdyA.js → quadrantDiagram-ABIIQ3AL-CVF1CNNO.js} +1 -1
  78. package/web-dist/assets/{radar-I7S5WNFK-C10EMwWx.js → radar-I7S5WNFK-Co7Otr5p.js} +1 -1
  79. package/web-dist/assets/{railroad-3IZDKUUU-BYegvtJS.js → railroad-3IZDKUUU-BTwZqHtN.js} +1 -1
  80. package/web-dist/assets/railroad-abnf-AHOZXSZD-CEkmgARz.js +1 -0
  81. package/web-dist/assets/railroad-ebnf-EBAXGLYW-D2dXzrFd.js +1 -0
  82. package/web-dist/assets/railroad-peg-LSFZ7HO6-yA2XVaxN.js +1 -0
  83. package/web-dist/assets/{railroadDiagram-RFXS5EU6-CfpRdkA8.js → railroadDiagram-RFXS5EU6-DCymlvVU.js} +1 -1
  84. package/web-dist/assets/{requirementDiagram-TGXJPOKE-CUajgDbc.js → requirementDiagram-TGXJPOKE-CEFbAu30.js} +1 -1
  85. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DRs5oKTx.js → sankeyDiagram-HTMAVEWB-fnX7WrAj.js} +1 -1
  86. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-BTeEqLFC.js → sequenceDiagram-DBY2YBRQ-BUvsAYkQ.js} +1 -1
  87. package/web-dist/assets/{src-Cf1DXZ3M.js → src-V5VjKILh.js} +1 -1
  88. package/web-dist/assets/{stateDiagram-2N3HPSRC-BPcqMj-B.js → stateDiagram-2N3HPSRC-C9LJKbHM.js} +1 -1
  89. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-Bm1cPGFT.js +1 -0
  90. package/web-dist/assets/{swimlanes-5IMT3BWC-CJTd58TJ.js → swimlanes-5IMT3BWC-aq9B5aBs.js} +1 -1
  91. package/web-dist/assets/swimlanesDiagram-G3AALYLV-DR6Yx_Ic.js +8 -0
  92. package/web-dist/assets/{timeline-definition-FHXFAJF6-CqbaBVpe.js → timeline-definition-FHXFAJF6-DhJ9Jf4f.js} +1 -1
  93. package/web-dist/assets/{treeView-QDETBFTQ-B2OQ8r59.js → treeView-QDETBFTQ-D42FrEH4.js} +1 -1
  94. package/web-dist/assets/{treemap-6X3UGDF4-DGpAdWTA.js → treemap-6X3UGDF4-BV_9JyG6.js} +1 -1
  95. package/web-dist/assets/{vennDiagram-L72KCM5P-BAS_eWHm.js → vennDiagram-L72KCM5P-BkwHzxsN.js} +1 -1
  96. package/web-dist/assets/{wardley-OPB4EBWU-DpNHIH3W.js → wardley-OPB4EBWU-fwm52ei3.js} +1 -1
  97. package/web-dist/assets/{wardleyDiagram-EHGQE667-2N7X1vj8.js → wardleyDiagram-EHGQE667-D_UqvgcE.js} +1 -1
  98. package/web-dist/assets/{xychartDiagram-FW5EYKEG-C8mfLCei.js → xychartDiagram-FW5EYKEG-tKdmcTfS.js} +1 -1
  99. package/web-dist/index.html +2 -2
  100. package/web-dist/assets/architecture-TIHT7OUA-CXyjbDft.js +0 -1
  101. package/web-dist/assets/channel-uFMfT294.js +0 -1
  102. package/web-dist/assets/classDiagram-OUVF2IWQ-D1koMbqS.js +0 -1
  103. package/web-dist/assets/classDiagram-v2-EOCWNBFH-D1koMbqS.js +0 -1
  104. package/web-dist/assets/eventmodeling-45OFAUF4-tWFB9ALb.js +0 -1
  105. package/web-dist/assets/flowDiagram-23GEKE2U-Dz_Opd7B.js +0 -1
  106. package/web-dist/assets/index-BwC7xvWu.js +0 -336
  107. package/web-dist/assets/index-CuXKWysP.css +0 -2
  108. package/web-dist/assets/railroad-abnf-AHOZXSZD-DLmknJDm.js +0 -1
  109. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BoZF0VpA.js +0 -1
  110. package/web-dist/assets/railroad-peg-LSFZ7HO6-Dib7YVqg.js +0 -1
  111. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-DiV9dOER.js +0 -1
  112. package/web-dist/assets/swimlanesDiagram-G3AALYLV-BDeobZmn.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz-server",
3
- "version": "0.15.20",
3
+ "version": "0.15.23",
4
4
  "private": false,
5
5
  "description": "Replaceable Frizz server runtime",
6
6
  "license": "MIT",
@@ -271,8 +271,12 @@ being indistinguishable from bypass.
271
271
  THE TABLE ships UNIVERSAL rules only; this plugin loads for every project frizz drives, so a rule that
272
272
  is right for one repo is wrong for the next. `catastrophic-delete` and `raw-disk-write` deny outright
273
273
  (strictly safer than bypass, which would have allowed both). `restrictive-mode` DEFERS whenever
274
- `permission_mode !== "auto"`, which is what makes a genuine lower-permission mode usable: move a thread
275
- to `default` with the live permission control and its prompts come back. `FRIZZ_PERM_POLICY=review`
274
+ `permission_mode` is anything but `auto` or `bypassPermissions`, which is what makes a genuine lower-permission mode usable: move a thread
275
+ to `default` with the live permission control and its prompts come back. (Until 2026-10-10 the test
276
+ was `!== "auto"`, which swept in `bypassPermissions` — the one mode MORE permissive than auto — so a
277
+ bypass thread was handed every prompt Claude Code still raises under bypass, as approval cards an auto
278
+ thread never showed. Bypass now falls through to `worker-autonomy`; any other or unknown mode still
279
+ defers.) `FRIZZ_PERM_POLICY=review`
276
280
  defers everything. Fail-safe INVERTS the old observer's fail-open — for a hook that can APPROVE, any
277
281
  error must fall back to asking, never to allowing.
278
282
 
@@ -1179,3 +1183,39 @@ The maintainer, after a worker asked to send an audit's results to a resting thr
1179
1183
  - **`spawn_thread` now names the spawner at the end of the child's prompt**, so a spawned thread asked for results can `steer` them back. Its "results NEVER come back to you" premise was true only because nothing could carry them; the doctrine (last resort, do-or-ask first) is unchanged.
1180
1184
  - **Inside one session: measured, then taught.** Measured live on Claude Code 2.1.295 (2026-10-09, headless `claude -p` sessions, read off the sub-agent transcripts and token files each recipient wrote): a parent's `SendMessage` to a RUNNING child's full agent id is delivered when the child's current tool call returns; a child's `SendMessage` to a running sibling's id is delivered the same way, and to a finished sibling's id resumes it. Only the full id resolves — a prefix, the description or the `subagent_type` answers `No agent named '<x>' is reachable`. Children have no `ListAgents`, so a sibling is reachable only by an id someone hands it, or by replying to the `from=` of a message it received. The epilogue gains one sentence for the sideways channels; the worker contract gains one paragraph under Sub-agents and a short section for `steer`.
1181
1185
  - **Rejected: a separate tag under the `<frizz-…>` prefix.** That prefix is Frizz's plumbing (`NOISE_PREFIXES`), which the transcript drops whole; the first draft's `<frizz-thread-message>` vanished from the chat entirely.
1186
+
1187
+ ## 2026-10-11: the plugin is loaded through a path that moves — so a hook now runs under daemons older than itself
1188
+
1189
+ The maintainer, looking at the Reload plugins button on a thread: *"Do we need the reload plugins button? Shouldn't this be happening automatically?"* It should, and it could not: a worker was launched against its server generation's own `runtime/cc-worker`, which never changes, so the button re-read the same bytes on every installed Frizz and a live thread kept the previous version's plugin until it restarted. `stable-plugin-path.ts` had been written for this on 2026-07-31 and never wired (`plans/live-worker-updates.md`).
1190
+
1191
+ - **A worker is launched against `<cache>/current/cc-worker`**, a link the running server repoints at a staged copy of its own plugin (`workerPluginDir()` in `packages/server/src/dispatch.ts`). The frizz MCP shim's path is the exception and stays the generation's own: a reload does not re-spawn an MCP server, so a moving path would buy it nothing.
1192
+ - **Each worker's daemon re-reads the plugin closure by itself, between turns**, when its fingerprint changed (`packages/server/src/backend/claude-plugin-sync.ts`). The button, its phone row and the `reloadThreadPlugins` RPC are deleted.
1193
+ - **The closure it watches includes the operator's own**: `skills/<name>/SKILL.md`, `agents/**/*.md`, `commands/**/*.md`, the `enabledPlugins` of the settings files and `plugins/installed_plugins.json`, under both the project's `.claude` and the operator's. The button was covering this too, and not incidentally — measured on claude 2.1.296, a project skill written after a session started was NOT invocable a turn later, and a reload picked up a new skill, agent and command alike (40–85ms each). The fingerprint is stats, 1–4ms on the maintainer's machine; settings files are read for that one key rather than stat'ed, because Claude Code rewrites `settings.local.json` on every permission grant.
1194
+ - **THE CONTRACT THIS CHANGES FOR EVERY FILE IN THIS DIRECTORY.** A hook script used to run only under the daemon that was forked beside it, by the same version, with the environment that version composed. It now runs under whatever daemon is alive: `${CLAUDE_PLUGIN_ROOT}/hooks/x.mjs` resolves through the link at each run, so the moment a new Frizz boots, its hook scripts execute inside sessions an OLDER Frizz forked, with the older worker environment (`FRIZZ_THREAD`, `FRIZZ_PERM_DIR`, `FRIZZ_NATIVE_ASK` and whatever came after) and, until that session's next turn boundary, the older `hooks.json` registrations. So a hook that starts reading a new environment variable must behave sensibly when it is absent, and a hook must not assume a sibling registration added in the same release is already armed. The reverse skew is gone in exchange: a live worker no longer runs last month's hook against this month's server.
1195
+ - **Measured against real claude 2.1.296** (`_live_plugin_sync.mts`, and its `--control` with the sync off): after a repoint the next prompt's hook ran the NEW script in both runs, which is the link alone; a `Stop` registration and a skill that exist only in the new version were live only with the sync on. Claude Code does not re-read a `--plugin-dir` plugin by itself.
1196
+ - **What a reload costs a conversation, since every live worker now does it unasked: no prompt-cache miss.** Read off the same two runs' transcripts, request by request: the first request after each reload read exactly the cached prefix the control's did (12,763 tokens, then 25,607 against the control's 19,964 a turn later, the difference being what the earlier reload had added) and WROTE about 5,500 more — the skill listing, re-announced as an attachment at the end of the conversation rather than changed in place at its head. So a reload is one listing's worth of cache write per live thread, once per change. The one thing that would invalidate the head is a changed MCP tool list, which a reload does not touch.
1197
+ - **Rejected: staging a source checkout too.** A dev server's `cc-worker/` is edited where it stands, so a worker launched against it already sees every script edit, and the daemon's fingerprint (size and mtime of every file) picks up the rest at the next turn boundary. A staged copy would only be a stale snapshot between the editor and the worker.
1198
+ - **Rejected: having the server ask.** The plan had the server reload every idle thread after an update. The server's reading of "idle" is the board's, a beat behind the session; the daemon's is exact, and it lets an input wait behind a reload instead of racing it.
1199
+
1200
+ ## 2026-10-11: a Codex thread's hooks ride every resume — so SessionStart(resume) fires there, and must say nothing
1201
+
1202
+ The same question, asked of Codex: *"We need to make sure that it works on Claude Code and Codex."* It did not, and the reason was older than the question. A Codex thread has no plugin; its hooks are per-thread config, and Codex rebuilds a thread from the config of whichever request loads it. Frizz sent the hooks with `thread/start` alone, so the first `thread/resume` after any Frizz restart rebuilt the thread with an MCP mount and nothing else: no scratch-directory nudge, no `bash-background` guard, no SubagentStart epilogue, for the rest of the thread's life and with no error anywhere.
1203
+
1204
+ - **The bridge sends them now, with the start and every resume** (`codexWorkerThreadConfig`, handed to the bridge as `workerConfig`). `ARCHITECTURE.md` § Stable server updates has the rule for when a resume rebuilds a thread and how Frizz makes it happen between turns.
1205
+ - **So `SessionStart` now fires with `source: "resume"` on Codex, which it never could.** `scratchpad.mjs` treats a resume as lost context and says so: *"Context was just lost…"*. On Codex that is false every time. A resumed thread is rebuilt from its rollout with the whole conversation in it (measured 2026-10-11 on codex 0.161.0: the request after two rebuilds still carried every earlier turn), and Frizz resumes a thread routinely, at its first message after each restart and again when a newer Frizz hands it new hooks.
1206
+ - **The Codex registration says so with a flag, `--resume-keeps-context`, and the hook exits silently for it.** A compaction still re-orients on Codex as anywhere, and a fresh start is still told the directory exists. Claude's registration does not carry the flag: a Claude resume is a cold start of the process, and it keeps the wording.
1207
+ - **Rejected: leaving `SessionStart` out of the bag a resume carries.** The bag replaces the thread's config whole, so a resume without the registration is a thread with no `SessionStart(compact)` either, which is the one channel that re-orients a Codex worker after a compaction.
1208
+ - **Rejected: teaching the hook that the caller is Codex by some other sign** (the `--session` flag, a transcript path under `~/.codex`). Those say who is calling, not what a resume means there. A flag that names the property survives the day a second backend has the same one.
1209
+
1210
+ ## 2026-10-11: a worker reaches Frizz through `frizzctl`, a command line, and no longer through MCP tools
1211
+
1212
+ The sixteen worker capabilities (`ask`, `unask`, `secret`, `done`, `activity`, `watch`, `unwatch`, `watch-pr`, `watch-issue`, `timer`, `goal`, `title`, `link`, `unlink`, `steer`, `spawn-thread`) were MCP tools, `mcp__frizz__<name>`. They cost about 12k tokens of schema that each provider loads its own way, could not be composed with anything, and had to be mounted three different ways (a `--mcp-config` file for Claude, `-c mcp_servers.…` for Codex, `session/new` for ACP). Each is now a subcommand of `bin/frizzctl`, which a worker runs from its own shell on every backend. The reasoning is in the header of `bin/frizzctl.mjs`.
1213
+
1214
+ - **One registry, two front-ends.** `lib/frizz-tools.mjs` holds each command's definition, its long description, its handler and how its arguments are spelled. `bin/frizzctl` is the command line over it, and `bin/frizz-mcp.mjs` is the same registry over MCP stdio, kept only for a thread that was started under the old contract.
1215
+ - **Every text a worker reads names the command.** The contract (`packages/server/src/workerPrompt.ts`), the hook messages here, the `frizz:gh` skill and every wake, bump and refusal the server composes say `frizzctl <command>`. Dated entries above this one keep the `mcp__frizz__*` names they were written with.
1216
+ - **The contract states the rule and the command's own `--help` carries the detail.** `frizzctl --help` is a short index; `frizzctl <command> --help` prints the description that used to be that tool's MCP description. A worker reads the long text of the command it is about to run and of no other.
1217
+ - **Prose goes through a file or a quoted heredoc.** A handoff body is full of backticked paths and commands, and inside a double-quoted argument the shell would run them. Every text argument takes `--<name>-file <path|->`, and the contract teaches `<<'EOF'`.
1218
+ - **The caller is the environment's.** A handler reads `FRIZZ_THREAD` (or the explicit `FRIZZ_THREAD_SLUG` stamp) and never an argument, so a command line cannot name another thread. A sub-agent's shell inherits it and acts as its parent's thread.
1219
+ - **Exit 2 is a refusal, not a failure.** `frizzctl done` with a question open or a watch armed prints what blocks it and exits 2; a fault or a misuse exits 1.
1220
+ - **A worker's own command never becomes an approval card.** Dispatch pre-approves `Bash(frizzctl:*)`, the promise the MCP server's own pre-approval made: on a thread a human moved to `default`, the worker can still ask, register and sign off unasked. Measured against a real worker on Claude Code 2.1.296, the rule carries every form the contract teaches except a quoted heredoc whose body is a JSON object, which Claude Code's own command checks send to a prompt — and that is the form `frizzctl ask` needs. `hooks/perm-policy.mjs` approves exactly that shape itself (`own-frizzctl`: `frizzctl`, words the shell does not interpret, `<<'WORD'`, a literal body, nothing after the terminator) and nothing wider.
1221
+ - **Which threads still get the MCP server.** A Claude worker is handed the contract at every fork, so a daemon forked now never gets it. A Codex thread's contract is its base instructions and an ACP thread's is its first prompt, both fixed when the thread started, so the session row records which surface the thread was taught (`worker_surface`) and only a thread taught MCP is still handed the server, on its own thread config. Every thread, old or new, gets `frizzctl` on its shell's PATH.