/ utils / planModeV2.ts
planModeV2.ts
 1  import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
 2  import { getRateLimitTier, getSubscriptionType } from './auth.js'
 3  import { isEnvDefinedFalsy, isEnvTruthy } from './envUtils.js'
 4  
 5  export function getPlanModeV2AgentCount(): number {
 6    // Environment variable override takes precedence
 7    if (process.env.CLAUDE_CODE_PLAN_V2_AGENT_COUNT) {
 8      const count = parseInt(process.env.CLAUDE_CODE_PLAN_V2_AGENT_COUNT, 10)
 9      if (!isNaN(count) && count > 0 && count <= 10) {
10        return count
11      }
12    }
13  
14    const subscriptionType = getSubscriptionType()
15    const rateLimitTier = getRateLimitTier()
16  
17    if (
18      subscriptionType === 'max' &&
19      rateLimitTier === 'default_claude_max_20x'
20    ) {
21      return 3
22    }
23  
24    if (subscriptionType === 'enterprise' || subscriptionType === 'team') {
25      return 3
26    }
27  
28    return 1
29  }
30  
31  export function getPlanModeV2ExploreAgentCount(): number {
32    if (process.env.CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT) {
33      const count = parseInt(
34        process.env.CLAUDE_CODE_PLAN_V2_EXPLORE_AGENT_COUNT,
35        10,
36      )
37      if (!isNaN(count) && count > 0 && count <= 10) {
38        return count
39      }
40    }
41  
42    return 3
43  }
44  
45  /**
46   * Check if plan mode interview phase is enabled.
47   *
48   * Config: ant=always_on, external=tengu_plan_mode_interview_phase gate, envVar=true
49   */
50  export function isPlanModeInterviewPhaseEnabled(): boolean {
51    // Always on for ants
52    if (process.env.USER_TYPE === 'ant') return true
53  
54    const env = process.env.CLAUDE_CODE_PLAN_MODE_INTERVIEW_PHASE
55    if (isEnvTruthy(env)) return true
56    if (isEnvDefinedFalsy(env)) return false
57  
58    return getFeatureValue_CACHED_MAY_BE_STALE(
59      'tengu_plan_mode_interview_phase',
60      false,
61    )
62  }
63  
64  export type PewterLedgerVariant = 'trim' | 'cut' | 'cap' | null
65  
66  /**
67   * tengu_pewter_ledger — plan file structure prompt experiment.
68   *
69   * Controls the Phase 4 "Final Plan" bullets in the 5-phase plan mode
70   * workflow (messages.ts getPlanPhase4Section). 5-phase is 99% of plan
71   * traffic; interview-phase (ants) is untouched as a reference population.
72   *
73   * Arms: null (control), 'trim', 'cut', 'cap' — progressively stricter
74   * guidance on plan file size.
75   *
76   * Baseline (control, 14d ending 2026-03-02, N=26.3M):
77   *   p50 4,906 chars | p90 11,617 | mean 6,207 | 82% Opus 4.6
78   *   Reject rate monotonic with size: 20% at <2K → 50% at 20K+
79   *
80   * Primary: session-level Avg Cost (fact__201omjcij85f) — Opus output is
81   *   5× input price so cost is an output-weighted proxy. planLengthChars
82   *   on tengu_plan_exit is the mechanism but NOT the goal — the cap arm
83   *   could shrink the plan file while increasing total output via
84   *   write→count→edit cycles.
85   * Guardrail: feedback-bad rate, requests/session (too-thin plans →
86   *   more implementation iterations), tool error rate
87   */
88  export function getPewterLedgerVariant(): PewterLedgerVariant {
89    const raw = getFeatureValue_CACHED_MAY_BE_STALE<string | null>(
90      'tengu_pewter_ledger',
91      null,
92    )
93    if (raw === 'trim' || raw === 'cut' || raw === 'cap') return raw
94    return null
95  }