mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-27 15:13:37 +08:00
Resolves the only path that could plausibly cut coordinator-mode token
cost without changing architecture. Coordinator mode normally rejects
implicit forks (the orchestrator is supposed to delegate to typed
workers); this opt-in carves out a narrow window for cache-sharing
research fan-out only.
Why
===
Coordinator-mode parallel research dispatches several workers of the
same type. Each worker prefills its own system prompt + full tool
schema, easily 10-30k tokens × N workers. Fork's byte-identical API
prefix bypasses that entirely — the parent's prompt cache is fully
reused, marginal cost is just the directive bytes. On a 3-worker
research fan-out we expect ~10x prefill savings for the research
phase.
Out of scope: implementation/verification spawns still go through
typed workers (independent context, independent cache). This PR is
about research only.
What's in this PR
=================
- forkSubagent.ts
- New helper: isCoordinatorResearchForkEnabled() — gated on
feature('FORK_SUBAGENT') + isCoordinatorMode() + non-interactive
+ CLAUDE_CODE_COORDINATOR_RESEARCH_FORK=1.
- New constant: COORDINATOR_RESEARCH_FORK_SUBAGENT_TYPE = 'fork'
(single source of truth for what callers spell as subagent_type).
- New ForkMode union: 'normal' | 'coordinator-research'.
- buildForkedMessages / buildChildMessage now take an optional mode
parameter (default 'normal'). 'coordinator-research' adds rule 11
to the boilerplate: "RESEARCH FORK: do not modify files; report
findings instead". The first 10 rules are byte-identical to the
normal mode (cache-friendly), and the framing tag at the top is
unchanged so isInForkChild's recursive-fork guard fires for both
modes.
- AgentTool.tsx
- Adds a coord-research-fork branch in the agent-resolution code:
`subagent_type === 'fork'` + isCoordinatorResearchForkEnabled() →
routes through the same fork path as an omitted subagent_type.
- The omitted-subagent_type path in coordinator mode is unchanged
(still requires explicit type, with the existing helpful error).
- Computed forkMode threads through to buildForkedMessages.
- invocationLimiter.ts
- 'fork' default cap = 10 (between verification's 5 and the 8
fallback). Generous for legitimate parallel research, tight enough
that pathological loops show up before the budget runs out.
- forkSubagent.test.ts (new, 11 cases)
- Env-flag gating across all combinations.
- Boilerplate rule-11 injection in research mode + byte-identical
first 10 rules across modes (cache contract).
- Recursive-fork guard fires for both mode variants.
- Subagent type constant matches the literal "fork".
- invocationLimiter.test.ts
- One new case: default cap for 'fork' is 10.
- scripts/dev-coordinator-e2e.ps1
- Adds 8th e2e step: type constant, boilerplate variants, recursion
guard cross-mode, env-flag gate.
Why opt-in (and double-gated)
=============================
- Lazy-delegation lint and specialist redirect are blacklists: specific
anti-patterns, near-zero false positives, safe to default on.
- Coordinator research fork is a positive-space affordance: "the
coordinator MAY use a fork for research." Defaulting it on would
encourage forks for spawns that legitimately want a clean context
(e.g. adversarial verification), which conflicts with coordinator's
independent-worker philosophy.
- Two gates needed: feature('FORK_SUBAGENT') is the bundle-time
feature flag (we share its plumbing), CLAUDE_CODE_COORDINATOR_RESEARCH_FORK=1
is the user-level opt-in. Both must be on.
Risks considered
================
- Boilerplate divergence between modes would defeat the parent prompt
cache. Mitigated: research-mode boilerplate IS the normal-mode
boilerplate plus rule 11 at the end; first 10 rules are byte-identical
(covered by a test asserting that property explicitly).
- Recursive-fork guard could miss the new boilerplate. Mitigated: the
framing tag at the top is unchanged across modes (covered by tests).
- Model abuses 'fork' for non-research spawns. Mitigated: rule 11 in
the boilerplate forbids file modifications + invocationLimiter cap
10 contains pathological loops + opt-in flag means nothing changes
for users who don't turn it on.
Verification
============
- src/tools/AgentTool/forkSubagent.test.ts ............. 11/11 pass
- src/tools/AgentTool/invocationLimiter.test.ts ........ 14/14 pass
- All B1+B2+B3+B4 unit tests .......................... 144/144 pass
- scripts/dev-coordinator-e2e.ps1 ..................... GREEN (8 steps)
- get_diagnostics on changed files .................... clean
Tested: bun test src/tools/AgentTool/forkSubagent.test.ts (11/11); all coordinator unit tests (144/144); scripts/dev-coordinator-e2e.ps1 step 8 GREEN
Not-tested: live coordinator session with the flag on producing actual cache savings (telemetry-only follow-up; the flag is gated and the boilerplate contract is enforced by test)
Confidence: high
Scope-risk: narrow
Constraint: must NOT change buildChildMessage's first 10 rules (cache identity); must NOT change the boilerplate framing tag (recursion guard); must NOT widen the gate to non-coordinator paths (fork stays mutex with normal coordinator delegation by default)
Rejected: defaulting the gate on (positive-space affordance, false-positive cost too high); adding `fork_research` as a new AgentTool input parameter (schema change rejected on B2 by the same logic — too much blast radius); changing fork's tool pool to forbid Edit/Write in research mode (would break useExactTools cache identity; soft-constrained by rule 11 instead)
Directive: when telemetry shows enough adoption to validate the cache savings, consider exposing a /coord-fork slash command alongside the env flag for easier experimentation; do NOT promote to default-on without that data
Co-authored-by: 你的姓名 <you@example.com>