feat(tools): opt-in dispatcher surface — gitea + gitea_help (#51) #54

Merged
dsh merged 3 commits from experiment/dispatcher into master 2026-10-24 22:49:58 +00:00
Owner

What

Adds a third, opt-in tool surface: preset: dispatcher registers two meta-tools instead of the typed set.

  • gitea { operation, args } — validates args against that operation's schema, then runs the same execution path as the typed tool.
  • gitea_help { operation } — returns the operation's description and compact JSON Schema as a tool result.

full remains the default and is unchanged. Closes #51 (spike complete, decision: ship opt-in).

Why ship it

Measured on 2026-10-25 with five real triage tasks across three projects, five cold agents per arm (full method in #51):

full (96 tools) dispatcher (2 tools)
tool block 65,159 B 8,973 B
tokens / step 58.2k 41.1k (−29 %)
total tokens 1.92 M 1.77 M (−8 %)
steps 33 43 (+30 %)

It works functionally (5/5 tasks correct via 2 tools) and every step is cheaper; short tasks pay more steps, long sessions amortise them. So it ships as a selectable preset, not as the default and not as a "token saving" claim.

How

  • lib/dispatcher.js (pure): one-line catalog generated from TOOL_DEFS, per-operation help capped at 1.5 KB, minimal validation (required / type / enum / unknown-argument). No second schema corpus.
  • lib/index.js: the canonical execute path is extracted into executeCanonicalTool(), shared by typed tools and dispatch so they cannot drift. Registration is still driven by applyToolSelection() with the same signature guard.
  • lib/selection.js: dispatcher added to the accepted preset names; it resolves to no canonical tools because the two meta-tools are registered instead.
  • Registry stays stable — no dynamic registration — so the request prefix stays cacheable (the reason this beat #50's lazy loading).

Verification

  • test/dispatcher.test.mjs (8 tests): catalog covers all 96 operations one line each; help ≤ 1.5 KB for every operation; validation catches missing/typed/enum/unknown args; preset: dispatcher boots exactly gitea + gitea_help; default full still serialises to the same 65,159 B.
  • npm run check: 188 tests, leak scan clean, syntax clean.
  • Live: switching the checkout flipped the running plugin to the two tools and a probe agent reported exactly gitea, gitea_help; reverting restored the 96 typed tools.

Notes

  • Default unchanged: full. This is additive.
  • No per-tool UI presentation and no per-operation permission rules for dispatched calls — acceptable for an opt-in mode, documented in the README bullet.
## What Adds a third, **opt-in** tool surface: `preset: dispatcher` registers two meta-tools instead of the typed set. - `gitea { operation, args }` — validates `args` against that operation's schema, then runs the same execution path as the typed tool. - `gitea_help { operation }` — returns the operation's description and compact JSON Schema as a tool result. `full` remains the default and is unchanged. Closes #51 (spike complete, decision: ship opt-in). ## Why ship it Measured on 2026-10-25 with five real triage tasks across three projects, five cold agents per arm (full method in #51): | | full (96 tools) | dispatcher (2 tools) | |:--|--:|--:| | tool block | 65,159 B | **8,973 B** | | tokens / step | 58.2k | **41.1k (−29 %)** | | total tokens | 1.92 M | **1.77 M (−8 %)** | | steps | 33 | 43 (+30 %) | It works functionally (5/5 tasks correct via 2 tools) and every step is cheaper; short tasks pay more steps, long sessions amortise them. So it ships as a selectable preset, not as the default and not as a "token saving" claim. ## How - `lib/dispatcher.js` (pure): one-line catalog generated from `TOOL_DEFS`, per-operation help capped at 1.5 KB, minimal validation (required / type / enum / unknown-argument). No second schema corpus. - `lib/index.js`: the canonical execute path is extracted into `executeCanonicalTool()`, shared by typed tools and dispatch so they cannot drift. Registration is still driven by `applyToolSelection()` with the same signature guard. - `lib/selection.js`: `dispatcher` added to the accepted preset names; it resolves to no canonical tools because the two meta-tools are registered instead. - Registry stays **stable** — no dynamic registration — so the request prefix stays cacheable (the reason this beat #50's lazy loading). ## Verification - `test/dispatcher.test.mjs` (8 tests): catalog covers all 96 operations one line each; help ≤ 1.5 KB for every operation; validation catches missing/typed/enum/unknown args; `preset: dispatcher` boots exactly `gitea` + `gitea_help`; default `full` still serialises to the same 65,159 B. - `npm run check`: 188 tests, leak scan clean, syntax clean. - Live: switching the checkout flipped the running plugin to the two tools and a probe agent reported exactly `gitea, gitea_help`; reverting restored the 96 typed tools. ## Notes - Default unchanged: `full`. This is additive. - No per-tool UI presentation and no per-operation permission rules for dispatched calls — acceptable for an opt-in mode, documented in the README bullet.
Spike branch only; never tagged or published until the #51 gate is met.

- lib/dispatcher.js (pure): one-line catalog generated from TOOL_DEFS,
  per-operation help capped at ~1.5 KB, minimal argument validation
  (required/type/enum + unknown-argument reporting).
- lib/index.js: preset 'dispatcher' registers gitea + gitea_help instead
  of the typed set. Registry is stable (no dynamic registration), schemas
  arrive as append-only tool results, so the request prefix stays
  cacheable. The canonical execute path is extracted into
  executeCanonicalTool() and shared, so dispatch and typed calls cannot
  drift.
- Default behaviour unchanged: full still resolves to the same 96
  definitions in the same order (65,159 B measured by the test).
- Measured surface: full 65,159 B vs dispatcher 8,973 B = 7.3x smaller
  (issue predicted 6.7x). Tests: 188 total, 8 new for the spike.
Same guard as test/tool-surface.test.mjs: only the two boot-level spike
assertions import lib/index.js; the pure catalog/help/validation checks run
everywhere.
Five real triage tasks x two arms (full vs dispatcher), cold agents.
Steps 33->43, calls 31->43, arg errors 0->10, total tokens -8 %,
tokens/step -29 %. The payload delta is cache-read tokens, so the cost
result is a wash on short tasks and the README must not claim a token
saving. Crossover ~8-10 steps.
dsh merged commit 5342ea66e3 into master 2026-10-24 22:49:58 +00:00
Sign in to join this conversation.
No description provided.