feat(context-management): provider-중립 context management — 도구루프 turns 누적 경계 (#27 cutoff-gap)#64
Conversation
도구 루프의 무제한 turns 누적(tool_result·history) 해소 — provider-중립 정책 + 2개 실행 경로(anthropic native context_management 위임 / 나머지 3사 client-side 가지치기). 현행문서 검증(3사 native 가 각기 다른 API 표면·anthropic 만 현재 엔드포인트서 도달) + 추측 6건 검증(CM⊥schema 구조적 분리·stateless per-request· cache 공존). default-on(보수 트리거 150k·keep 3), clear_tool_uses 만(슬라이스1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task1 context.ts(가지치기)·Task2 loop 라우팅·Task3 anthropic native·Task4 4게이트. 각 태스크 RED→GREEN→커밋. 스펙 전 섹션 커버리지·타입 일관성 자체검토 완료. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 가지치기 ContextManagementPolicy(providers/types.ts) + tools/context.ts(approxTokens· pruneToolResults·DEFAULT_CONTEXT_POLICY). 오래된 tool_result.content 를 stub 치환 (블록 제거 아님 → 페어링·순서·서명 불변)·최근 keep 보존·idempotent·in-place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
빈 turns·keep≥결과수(임계초과 no-op)·keep 0(전체정리)·string-content 혼합·early-return 5 케이스 추가(코드 품질 리뷰 M2/M3 반영). pruneToolResults 로직 불변. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ApiProvider.nativeContextManagement 플래그 + ApiCallOptions.contextManagement + ToolLoopDeps.contextPolicy. loop 가 provider 분기 없이 플래그만 보고 native 엔 opts.contextManagement 위임·그 외엔 pruneToolResults. default-on(미지정→기본·null→비활성). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…권 명시 코드 품질 리뷰 반영: loop 가 contextManagement 소유(caller opts 값 delete 로 누출 차단) + 의도된 native turns 무제한 성장 주석. native 테스트를 keep=1·2결과·임계초과 입력으로 강화해 native 가 client-side prune 을 타지 않음을 실증(기존 trivial 입력은 회귀 미포착). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0 fallback nativeContextManagement:true 노출 + opts.contextManagement → body.context_management (clear_tool_uses_20250919) + anthropic-beta: context-management-2025-06-27 헤더(CM 있을 때만 = 무회귀). CM 400 시 context_management+beta strip-retry(CM⊥schema 구조적 분리로 schema fallback 과 조합 안전). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
코드 품질 리뷰 M1 반영 — CM 동봉 스트리밍 요청이 400 시 context_management+beta 제거 후 재시도해 readStream 으로 클린 응답을 돌려줌을 실증(버퍼 경로만 있던 커버리지 보강). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
keep 윈도 내 단일 거대 tool_result·비-tool_result content 미정리는 native clear_tool_uses 시맨틱과 동일한 의도된 비범위임을 doc-comment 에 명시. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c275049ca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| delete chatOpts.contextManagement // loop 가 소유 — caller 가 실어보낸 값은 쓰지 않는다(누출 차단) | ||
| if (policy) { | ||
| if (provider.nativeContextManagement) chatOpts.contextManagement = policy | ||
| else pruneToolResults(turns, policy) |
There was a problem hiding this comment.
Preserve unsent parallel tool results
For non-native providers, this pruning runs at the start of the next loop iteration, before the model has seen the tool results appended by the previous iteration. If one assistant turn requests more than keepRecentToolUses tools and their outputs push the prompt over the trigger, the oldest result from that same just-executed batch is replaced with PRUNE_STUB before the follow-up provider.chat, so the model answers without an output it explicitly requested. Exclude the latest tool-result turn, or otherwise only prune results that have already been sent to the model at least once.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f86d1cd. pruneToolResults now excludes the most recent tool_result turn (the batch the previous iteration appended, not yet sent to the model) via lastToolResultTurnIndex, and only prunes already-sent results — so a parallel batch larger than keepRecentToolUses is preserved in full for the follow-up chat. Added a regression test (미전송 최신 tool_result 턴의 병렬 배치는 keep 초과여도 정리하지 않는다).
| const prunable = results.slice(0, Math.max(0, results.length - policy.keepRecentToolUses)) | ||
| for (const r of prunable) { | ||
| if (r.content === PRUNE_STUB) continue // idempotent | ||
| r.content = PRUNE_STUB |
There was a problem hiding this comment.
Avoid mutating committed history during pruning
This in-place replacement can mutate already-committed session history because createApiSession builds non-fresh working turns with a shallow copy of history; the ContentBlock objects are shared. When a non-native provider crosses the pruning threshold and the later chat/tool loop throws, the send is not committed, but prior history has still lost its original tool output, so a retry or follow-up sees stubs instead of the successful earlier result. Clone the historical blocks before pruning or defer this mutation until the send commits.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in f86d1cd. Switched to copy-on-write: on stub, the turn/content-array/block are cloned and only the turns slot is replaced, so the shared history ContentBlock objects (api-session working=[...history]) are never mutated. An uncommitted send that throws now leaves history intact; on success the commit (history=working) persists the pruned clones. Added a test asserting the shared block is unchanged while working carries the stub.
|
|
||
| /** default-on 도구루프 context management 기본 정책(보수값). engine 미지정 시 loop 가 적용. */ | ||
| export const DEFAULT_CONTEXT_POLICY: ContextManagementPolicy = { | ||
| triggerInputTokens: 150_000, |
There was a problem hiding this comment.
Trigger pruning before smaller context windows
Because the default policy only prunes non-native providers after the local estimate exceeds 150k tokens, any configured OpenAI/openai-compatible model with a smaller context window can hit the provider's context-length error before pruning ever runs. This leaves long tool sessions on those models failing instead of shedding old tool results; make this threshold provider/model-aware, or choose a default safely below the smallest supported non-native window while accounting for tool schema and output-token headroom.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Lowered the default trigger 150k→100k in f86d1cd, below the mainstream non-native windows (gpt-4o / gpt-4o-mini 128k) so pruning fires before context-overflow. A fully model-aware threshold needs a model→context-window table, which is the #13 capability-API track — out of scope for this slice; until then very-small/local windows and dense-token models use ToolLoopDeps.contextPolicy to lower it. Native (anthropic) uses the server-measured input_tokens trigger, so 100k is exact there. Documented in DEFAULT_CONTEXT_POLICY and the spec addendum.
… 100k P2#1 미전송 최신 tool_result 턴 제외: prune 이 iter 시작에 돌아 직전 배치는 미전송 → 한 어시스턴트 턴이 keep 초과 병렬 도구 호출 시 그 배치 일부가 모델이 보기 전 stub 되어 명시 요청 출력 없이 답하던 결함. lastToolResultTurnIndex 로 마지막 tool_result 턴 보존. P2#2 copy-on-write: api-session working=[...history] 가 블록 공유 → in-place 변이가 미커밋 send(throw)에도 history 손상(원자 커밋 위반). stub 시 턴·content·블록 클론. P2#3 트리거 150k→100k: 150k 는 gpt-4o/4o-mini(128k)보다 커서 작은-윈도 모델이 prune 전에 초과. 모델-인지 임계는 #13 후속(부분 완화), 작은-윈도는 contextPolicy 하향. TDD: context 12→14(미전송배치·copy-on-write 신규), client-side prune 테스트 ≥3 결과로 갱신. 4게이트 녹색(test 731/1 skip). 스펙에 "Codex 리뷰 반영" 부록. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f86d1cda99
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const lastTr = lastToolResultTurnIndex(turns) | ||
| const slots: Array<[number, number]> = [] | ||
| for (let i = 0; i < turns.length; i++) { | ||
| if (i === lastTr) continue |
There was a problem hiding this comment.
Stop treating historical tool results as unsent
For non-native providers, this also runs at the start of a later send, when turns has a new user prompt (and usually a final assistant response) after the previous tool-result turn. That last tool-result turn has already been sent to the model, but skipping it here makes the most recent historical result batch ineligible for pruning until another tool call happens; with a large prior parallel batch or keepRecentToolUses set to 0/small, the next request can still exceed the context limit despite the policy. Only exclude the tool-result turn when it is actually the last turn appended by the current tool-loop iteration.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7e758de. Refined the exclusion to the last turn only: replaced lastToolResultTurnIndex (last tool-result turn anywhere) with freshToolResultBatchSize, which is >0 only when turns[len-1] is itself a tool-result turn — i.e. the batch the current loop iteration just appended and has not yet sent. Since api-session always commits the final assistant turn into history, a new send's first prune sees the user prompt as the last turn → fresh=0 → the previous (already-sent) tool-result batch is eligible for pruning again. Added a test: 새 send 시작(마지막 턴=user 프롬프트)이면 이미 전송된 직전 tool_result 턴도 정리 대상이다.
| const chatOpts: ApiCallOptions = { ...opts, tools, toolChoice: 'auto' } | ||
| delete chatOpts.contextManagement // loop 가 소유 — caller 가 실어보낸 값은 쓰지 않는다(누출 차단) | ||
| if (policy) { | ||
| if (provider.nativeContextManagement) chatOpts.contextManagement = policy |
There was a problem hiding this comment.
Preserve fresh native tool results before enabling CM
When Anthropic returns more tool calls in one assistant turn than keepRecentToolUses (default 3) and their large results push the next loop iteration over triggerInputTokens, passing the unchanged policy here lets server-side clear_tool_uses run before Claude has seen those just-appended tool results. The server only keeps the most recent N tool uses, so earlier results from the fresh batch can be replaced with placeholders and the model answers without outputs it explicitly requested; either disable native CM for the immediate follow-up or raise keep to cover the current result batch.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Verified clear_tool_uses_20250919 semantics against current docs (context7): keep.tool_uses.value retains the N most-recent tool_uses at the block level, with no turn-boundary/latest-batch protection — so a single assistant turn with more parallel tool calls than keep can have part of its fresh batch clipped server-side before Claude reasons over it. Confirmed real. Fixed in 7e758de: when routing to native, the loop passes keep = max(policy.keepRecentToolUses, freshToolResultBatchSize(turns)) for that request, so the server keeps at least the entire just-produced batch (symmetric with the client-side fix). Added a test: native: 미전송 fresh 배치가 keep 초과면 그 요청의 keep 을 배치 크기로 올린다.
context7 로 clear_tool_uses 시맨틱 검증: keep 은 tool_use 블록 단위 "최근 N개", 턴 경계· 최신 배치 보호 없음 → fresh 배치 클립 가능 확인. P2#A client "마지막 *턴*" 한정: 라운드1 의 "마지막 tool_result 턴 무조건 제외"는 새 send (working=[...history,newPrompt])의 이미 전송된 과거 배치까지 보호해 prune 불능이 됐다. lastToolResultTurnIndex→freshToolResultBatchSize(마지막 턴이 tool_result 일 때만 fresh). P2#B native fresh-batch keep 상향: server clear_tool_uses 도 keep 초과 병렬 배치를 모델이 보기 전 클립 → loop 가 native 에 keep=max(policy.keep, freshBatchSize) 로 요청 한정 상향. TDD: context +1(새-send 과거배치 정리)·loop +1(native fresh-batch keep)·기존 새-send client 테스트 갱신. 4게이트 녹색(test 733/1 skip). 스펙 부록 라운드2 추가. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e758dee71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| chatOpts.contextManagement = | ||
| fresh > policy.keepRecentToolUses ? { ...policy, keepRecentToolUses: fresh } : policy |
There was a problem hiding this comment.
Preserve prior kept results when bumping native keep
When the next Anthropic call follows a fresh tool_result batch, this uses max(fresh, keepRecentToolUses) for native keep, but Anthropic's keep counts the total most-recent tool uses. The client-side path excludes the fresh batch and still keeps keepRecentToolUses already-seen results, so a fresh batch of 5 with keepRecentToolUses: 3 sends keep: 5 natively and can let the server clear all three prior results that the policy/client path would have preserved; use fresh + policy.keepRecentToolUses for parity if those recent results should remain visible on the follow-up turn.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9921693. Changed native keep from max(fresh, policy.keep) to fresh + policy.keepRecentToolUses. The client path preserves the fresh batch (excluded) plus keepRecentToolUses already-seen results, so native now keeps the same total set (fresh + keep most-recent tool_uses). When fresh=0 (new send) it reduces to policy.keep. Test updated to assert keep=7 for a 5-result fresh batch with keep=2.
| delete body.context_management | ||
| delete headers['anthropic-beta'] | ||
| return send() |
There was a problem hiding this comment.
Preserve context management through schema fallback
When contextManagement and responseSchema are both set, a 400 caused by Anthropic rejecting output_config.format takes this branch first and permanently deletes context_management before the outer schema fallback can strip only the schema fields. The eventual successful retry therefore runs with both schema and CM removed, which regresses direct provider calls or tool-loop calls that rely on CM to keep large tool histories under the context limit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9921693. Replaced the nested sendCM/schema fallback with per-field isolation (the PR #63 pattern): on a 400 with both set, ①strip schema only and retry (CM preserved) → ②if still 400, restore schema + strip CM and retry (schema preserved) → ③strip both. So a schema-caused 400 no longer drops CM. Added tests for the schema-caused and CM-caused branches plus the still-400→ApiProviderError path.
| const fresh = freshToolResultBatchSize(turns) | ||
| chatOpts.contextManagement = | ||
| fresh > policy.keepRecentToolUses ? { ...policy, keepRecentToolUses: fresh } : policy | ||
| } else pruneToolResults(turns, policy) |
There was a problem hiding this comment.
Include tool definitions in the prune budget
For non-native providers this branch decides whether to prune from turns only, while the same request still includes the serialized tools list. With large MCP schemas/descriptions, approxTokens(turns) can remain below the trigger even though turns + tools exceeds the OpenAI/Gemini context window, so pruning never runs and the request can still fail with a context-length error; reserve space for tool definitions or include them in the estimate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9921693. The loop now computes toolsTokens = ceil(JSON.stringify(tools).length/4) once and passes it to pruneToolResults(turns, policy, overheadTokens); the trigger check is approxTokens(turns) + overheadTokens <= triggerInputTokens, so large MCP tool schemas are counted toward the budget and pruning fires before turns+tools exceeds the window. (system is already inside turns, so no double-count.) Test added with an ~8k-char tool definition.
| const block = content[j] as ToolResultBlock | ||
| if (block.content === PRUNE_STUB) continue // idempotent | ||
| // copy-on-write: 원본(공유) 객체를 변이하지 않도록 턴·content·블록을 클론해 turns 슬롯만 교체한다. | ||
| const newBlock: ToolResultBlock = { ...block, content: PRUNE_STUB } |
There was a problem hiding this comment.
Avoid expanding short tool results while pruning
When an old tool_result.content is shorter than PRUNE_STUB, replacing it here increases the prompt instead of shrinking it. In sessions with many tiny historical tool outputs and a large kept/fresh result pushing the estimate over the trigger, the pruning pass can make the request larger and still leave it over the provider's context limit; skip already-small results or use a shorter/empty marker for those cases.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 9921693. Added a skip: if (block.content.length <= PRUNE_STUB.length) continue — results already at/below the stub length are left as-is (replacing them would grow the prompt). The loop continues to later, larger prunable slots rather than returning, so it still sheds the big results. Test added with a 2-char result preserved while a large one is pruned.
P2#1 native keep 패리티: max(fresh,keep)→fresh+policy.keep (fresh>keep 시 직전 kept 손실). P2#2 anthropic CM+schema 필드 격리(PR #63 패턴): 둘 다 set·schema-유발 400 에서 CM 까지 함께 삭제하던 회귀 차단 — ①schema→②CM→③둘다 순으로 무고한 필드 보존. P2#3 tools 를 client prune 예산에: overheadTokens(=도구정의 추정)를 트리거에 합산 — 큰 MCP 스키마가 turns 만으론 임계 이하인데 윈도 초과시 prune 미발화하던 갭. P2#4 작은 결과 확장 방지: content.length ≤ PRUNE_STUB.length 면 건너뜀. 선제 opus 적대검증(7수정 정확·Critical 0) 보강: CM-유발 400(②③)·지속 400 throw 테스트 2건 + JSON.stringify(tools) 가드. 4게이트 녹색(test 733→739). 스펙 부록 라운드3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9921693369
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (typeof t.content === 'string') chars += t.content.length | ||
| else for (const b of t.content) chars += blockChars(b) | ||
| } | ||
| return Math.ceil(chars / 4) |
There was a problem hiding this comment.
Use a safer token estimate before pruning
Because non-native providers only call pruneToolResults when approxTokens(...) exceeds the policy threshold, dividing all JS character counts by 4 can undercount inputs whose tokenization is much denser than English, such as Korean/Chinese tool output or compact JSON/code. With the default 100k trigger, a 128k-context OpenAI-compatible request can be well over the model limit while this estimate is still below the threshold, so no old tool_result is stubbed and the next provider.chat fails with a context-length error. Please make this estimator conservative for dense/non-ASCII content, or lower the trigger used with it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 5b55ef8 — and you caught an inverted comment: the old note claimed undercounting was the "safe" direction, which is backwards for a trigger (if real tokens exceed the estimate, pruning fires too late and the next request overflows). approxTokens now counts ASCII at ~4 chars/token but non-ASCII (CJK etc., ~2-4x denser) at ≥1 token/char, so dense/non-ASCII content is no longer severely undercounted (ASCII-only inputs are byte-identical to before). Secondary effect: the Korean PRUNE_STUB is ~19 tokens, so the skip-small guard was switched from char-length to token-based (estTokens(content) <= estTokens(PRUNE_STUB)) to keep it from expanding short ASCII results. Added a CJK estimate test. Residual ASCII-dense code (~3 chars/token) is covered by the contextPolicy override / model-aware sizing (#13).
… P2) chars/4 는 비-ASCII(CJK 등 2~4배 조밀)·dense 콘텐츠를 심하게 과소추정한다. 트리거 용도엔 과소추정이 위험(실토큰>추정이면 prune 전 윈도 초과 — 원 주석의 "과소=안전"은 역논리). ASCII 는 ~4 char/token 유지, 비-ASCII 는 1 token/char 이상으로 보수 추정(ASCII 전용은 기존 동작 동일). 2차 효과로 한국어 PRUNE_STUB(~19토큰)이 비싸져 skip-small 을 char→토큰 기반 (estTokens(content) ≤ estTokens(STUB))으로 전환 — 짧은 ASCII 결과를 char 로만 보면 토큰상 외려 키우던 갭 차단. 4게이트 녹색(test 739→740: CJK 보수추정 신규). 스펙 부록 라운드4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
도구 루프(
tools/loop.ts)가tool_result+history 를 무제한 누적(최대 8라운드·send 간 영속)하던 갭을, provider-중립 정책 + 2개 실행 경로로 경계한다. 컷오프 갭 분석(현행 Anthropic/OpenAI/Google 문서 대조)이 지목한 medium·최고가치 항목(#27).핵심 통찰: 누적 문제는 4사 공통이나 native context management 기능은 provider마다 다른 API 표면에 있다 — 현행 문서 검증 결과 anthropic 만 Fleet 의 현재 엔드포인트(Messages API)에서 native 가 도달하고, OpenAI(Responses 전용)·Google(Managed Agents 전용)은 표면 마이그레이션 선행 필요. 그래서:
context_management(clear_tool_uses_20250919) 위임 — 서버 실측 토큰 트리거·thinking 서명/구조를 서버가 정확 처리, beta 헤더context-management-2025-06-27, CM 400 strip-retry fallback.loop.tsclient-side 가지치기 — 오래된tool_result.content를 stub 치환(블록 제거 아님 → tool_use↔tool_result 페어링·순서·thinking 서명 불변).라우팅은
ApiProvider.nativeContextManagementcapability 플래그만 검사(하드코딩 provider 분기 없음). default-on(보수 트리거 150k·keep 3,ToolLoopDeps.contextPolicy로 비활성/튜닝) — 평소 소형 세션엔 no-op(무회귀), 큰 세션에서만 동작.변경 파일
providers/types.ts—ContextManagementPolicy·ApiCallOptions.contextManagement·ApiProvider.nativeContextManagementtools/context.ts(신규) —DEFAULT_CONTEXT_POLICY·approxTokens·pruneToolResultstools/types.ts—ToolLoopDeps.contextPolicytools/loop.ts— capability 라우팅(loop 가 contextManagement 소유)providers/anthropic.ts— native 매핑 + 플래그 + CM 400 fallbackengine/IPC/preload/renderer/shared 무변경(default-on 은 loop 내장 기본값 상속).
무회귀
CM body+beta 헤더는
opts.contextManagement동봉 시에만 → 비-CM 요청 byte-identical. CM ⊥ responseSchema(모든 schema 호출은 bypassTools → 도구루프 우회)라 두 400-fallback 이 한 호출에 공존하지 않아 조합 안전.Test Plan
npm run typecheck·npm run lint(0 경고) ·npm test(729 pass / 1 skip) ·npm run build4게이트 녹색context_management.applied_edits[].cleared_input_tokens확인 / 실 openai·gemini 키 큰 도구출력 client-side stub비범위(후속)
clear_thinking·compact전략 ·cleared_input_tokens텔레메트리 · OpenAI/Gemini native(표면 전환 종속) · per-model 트리거 튜닝 · UI 노출.🤖 Generated with Claude Code