Skip to content

feat(context-management): provider-중립 context management — 도구루프 turns 누적 경계 (#27 cutoff-gap)#64

Merged
pdw96 merged 13 commits into
masterfrom
feat/context-management
Jun 15, 2026
Merged

feat(context-management): provider-중립 context management — 도구루프 turns 누적 경계 (#27 cutoff-gap)#64
pdw96 merged 13 commits into
masterfrom
feat/context-management

Conversation

@pdw96

@pdw96 pdw96 commented Jun 15, 2026

Copy link
Copy Markdown
Owner

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 전용)은 표면 마이그레이션 선행 필요. 그래서:

  • anthropic → native context_management(clear_tool_uses_20250919) 위임 — 서버 실측 토큰 트리거·thinking 서명/구조를 서버가 정확 처리, beta 헤더 context-management-2025-06-27, CM 400 strip-retry fallback.
  • openai / openai-compatible / googleloop.ts client-side 가지치기 — 오래된 tool_result.content 를 stub 치환(블록 제거 아님 → tool_use↔tool_result 페어링·순서·thinking 서명 불변).

라우팅은 ApiProvider.nativeContextManagement capability 플래그만 검사(하드코딩 provider 분기 없음). default-on(보수 트리거 150k·keep 3, ToolLoopDeps.contextPolicy 로 비활성/튜닝) — 평소 소형 세션엔 no-op(무회귀), 큰 세션에서만 동작.

변경 파일

  • providers/types.tsContextManagementPolicy · ApiCallOptions.contextManagement · ApiProvider.nativeContextManagement
  • tools/context.ts(신규) — DEFAULT_CONTEXT_POLICY · approxTokens · pruneToolResults
  • tools/types.tsToolLoopDeps.contextPolicy
  • tools/loop.ts — capability 라우팅(loop 가 contextManagement 소유)
  • providers/anthropic.ts — native 매핑 + 플래그 + CM 400 fallback

engine/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 build 4게이트 녹색
  • TDD: context 12 · loop 라우팅 5 · anthropic CM 5(버퍼+스트리밍 400-fallback)
  • subagent-driven(implementer→spec리뷰→코드품질리뷰) + opus 최종 통합 리뷰("Ready to merge")
  • 머지 전 Codex 봇 자동리뷰 대기·반영 (메타: 백로그 우선순위 / 로드맵 (코드 검증 기반) #27 워크플로)
  • 라이브 스모크(수동): 실 anthropic 키 긴 도구루프 → 응답 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

pdw96 and others added 9 commits June 15, 2026 22:56
도구 루프의 무제한 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/main/core/tools/loop.ts Outdated
delete chatOpts.contextManagement // loop 가 소유 — caller 가 실어보낸 값은 쓰지 않는다(누출 차단)
if (policy) {
if (provider.nativeContextManagement) chatOpts.contextManagement = policy
else pruneToolResults(turns, policy)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 초과여도 정리하지 않는다).

Comment thread src/main/core/tools/context.ts Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/core/tools/context.ts Outdated

/** default-on 도구루프 context management 기본 정책(보수값). engine 미지정 시 loop 가 적용. */
export const DEFAULT_CONTEXT_POLICY: ContextManagementPolicy = {
triggerInputTokens: 150_000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@pdw96

pdw96 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/main/core/tools/context.ts Outdated
const lastTr = lastToolResultTurnIndex(turns)
const slots: Array<[number, number]> = []
for (let i = 0; i < turns.length; i++) {
if (i === lastTr) continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 턴도 정리 대상이다.

Comment thread src/main/core/tools/loop.ts Outdated
const chatOpts: ApiCallOptions = { ...opts, tools, toolChoice: 'auto' }
delete chatOpts.contextManagement // loop 가 소유 — caller 가 실어보낸 값은 쓰지 않는다(누출 차단)
if (policy) {
if (provider.nativeContextManagement) chatOpts.contextManagement = policy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@pdw96

pdw96 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/main/core/tools/loop.ts Outdated
Comment on lines +109 to +110
chatOpts.contextManagement =
fresh > policy.keepRecentToolUses ? { ...policy, keepRecentToolUses: fresh } : policy

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/core/providers/anthropic.ts Outdated
Comment on lines +348 to +350
delete body.context_management
delete headers['anthropic-beta']
return send()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/main/core/tools/loop.ts Outdated
const fresh = freshToolResultBatchSize(turns)
chatOpts.contextManagement =
fresh > policy.keepRecentToolUses ? { ...policy, keepRecentToolUses: fresh } : policy
} else pruneToolResults(turns, policy)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@pdw96

pdw96 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/main/core/tools/context.ts Outdated
if (typeof t.content === 'string') chars += t.content.length
else for (const b of t.content) chars += blockChars(b)
}
return Math.ceil(chars / 4)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@pdw96

pdw96 commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: 5b55ef8b3e

ℹ️ 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".

@pdw96
pdw96 merged commit 931d193 into master Jun 15, 2026
2 checks passed
@pdw96
pdw96 deleted the feat/context-management branch June 15, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant