Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,81 @@ All notable changes to `xgen-agent-runtime` are recorded here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project adheres to [Semantic Versioning](https://semver.org/).

## [4.89.0] - 2026-10-12

### Changed: 컨텍스트 압축을 다시 짰다 (언제, 얼마나, 무엇을 남기는지)

2026-10-11 검토에서 압축이 너무 늦게 돌거나(한국어 과소 추정), 돌아도 대화를 잃거나(요약 실패 시 이력 삭제),
provider 의 "prompt too long" 에는 아무것도 하지 않는 문제를 찾았다. Codex · Claude Code 의 방식(provider 가 센
토큰을 기준으로 삼기, 넘침 시 압축 후 재시도, 사용자 말은 그대로 남기기)에 맞춰 고쳤다. 고친 뒤 세 차례 검토(두 번은
핵심 정책 · 호스트와 클라이언트 · 실제 파이프라인 시나리오의 세 갈래, 마지막은 확인 검토)에서 나온 문제도 이 판에서 고쳤다.

- **추정 하나** (`core.text_tokens`): 압축 판단과 호출 전 입력 맞춤이 같은 글자 부류 기반 추정을 쓴다. 예전 압축 쪽은
4자당 1토큰이라 한국어를 2~3배 적게 셌다. 요청에 붙는 것은 모두 센다(시스템 프롬프트, 도구, Stage 3 이 요청 사본에만
붙이는 턴 맥락: 날짜 · 고른 기억 · 턴 노트). 성공한 호출마다 provider 가 보고한 입력 토큰을 기준점으로 삼고
(`record_prompt_usage`, 캐시 읽기 · 쓰기 포함, Claude Code CLI 포함), 그 뒤에 붙은 것만 추정한다. 보정 비율은
0.9~2.5 로 묶고, 기준점 추정은 보정된 단순 추정의 80% 아래로 내려가지 않는다. 메시지별 추정은 객체마다 기억해
긴 대화에서 반복마다 전부 다시 세지 않는다(긴 글 캐시는 LRU, 마지막 메시지의 제자리 수정도 알아챈다).
- **입력 예산과 문턱** (`core.compaction`): 창에서 답 몫(`output_reserve`: max(4096, max_tokens + 2048), 창의 절반
이하)을 뺀 입력 예산을 기준으로, 압축이 줄일 수 없는 몫(시스템 프롬프트 · 도구 · 턴 맥락 · 지금 요청의 첨부,
`fixed_tokens`)을 뺀 나머지의 80% 를 넘으면 50% 까지 줄인다(`soft_level`). 큰 PDF 를 붙인 턴이 요약 직후에도 문턱을
넘어 요청마다 요약하던 일을 막는다. Stage 4 guard 도 같은 답 몫을 남기므로(`TokenBudgetGuard` 기본값 0 = 이 몫, 매
점검마다 상태에서 계산) 약한 압축이 늘 guard 보다 먼저 온다. 예전에는 창의 80% 기준이라 32k 창 · max_tokens 8192 에서
guard 가 68% 에서 먼저 터졌다. 결정적 정리(오래된 도구 결과 · 도구 호출 인자 · 이미지)를 먼저, 모자라면 모델 요약.
지금 답하는 요청에 붙은 이미지와 PDF 는 정리에서 지우지 않는다.
- **요약 횟수**: 약한 압박의 요약 시도는 요청 하나에 한 번, 턴당 6번이고 실패한 시도와 모델이 없는 경우도 센다. 두 번
실패하면 그 턴의 약한 압박은 모델을 더 부르지 않는다. 백그라운드 요약이 도는 동안에는 약한 압박이 같은 이력을 다시
요약하지 않는다. 요약할 구간이 창의 5%(작은 창은 최대 15%, 2,000 바닥)보다 작으면 하지 않는다(강한 압박도 같다).
Stage 16 의 압축 요청도 이 규칙을 따른다(사람이 거는 `/compact` 만 예외). 턴 계수는 `begin_turn` 이 비운다.
- **강한 압박** (guard · 넘침): 요약을 먼저(실패하면 결정적 다이제스트) 하고, 그래도 입력 예산을 넘을 때만 손실을
싼 것부터 낸다. 공격적 정리(오래된 이미지 · PDF, 오래된 결과 짧게), 남은 도구 결과(결과 속 이미지 먼저, 그다음 글을
비율대로 다시 재며), 마지막으로 지금 요청의 첨부를 하나씩. 어떤 단계든 그것으로 닫을 수 없는 차이면 건드리지 않고
guard 가 거절한다. 압축기가 예외를 내도 덜어 낸 만큼은 인정해 guard 가 다시 보고 Stage 6 이 다시 보낸다.
- **넘침 복구** (Stage 6): provider 가 창을 넘었다고 거절하면(OpenAI · vLLM · Gemini(AI Studio · Vertex) 400 도 이제
`TOKEN_LIMIT`) 강하게 압축하고 한 번 다시 보낸다. 오류에 적힌 실제 한도가 믿던 창보다 작으면 그 턴의 창을 그 값으로
바꾼다. 메시지가 이미 작고 답 몫 때문에 넘친 경우(vLLM "100 in the messages, 8192 in the completion")는 압축하지 않는다.
숫자를 적지 않는 넘침 문구에는 창 교정 없이 압축과 재시도만 한다.
- **요약 체크포인트** (`LLMSummaryCompactor`): 앞부분을 `[Context checkpoint]` 메시지 하나로 바꾼다. 요약은 사용자
요청 · 한 일 · 결정 · 문제 · 남은 일 · 현재 상태 여섯 항목이고, 도구 호출과 결과까지 읽는다(예전에는 글 블록만, 그것도
앞 12,000자만). 지금 하던 요청은 원문 그대로(압축 목표의 절반까지) 첨부(이미지 · PDF · 파일, 바로 앞에 따로 보낸
첨부 포함)와 함께 붙고, 같은 턴의 두 번째 압축에서도 따로 붙는다. 첨부만 보낸 요청도 요청이다. 그 밖의 사용자 말은 창의 8% 안에서 그대로 붙인다. 런타임이 넣는 안내(산출물 대조, 턴 예산)는
사용자 말로 보지 않고, 다른 대화로 바뀐 상태에서는 예전 고정 말을 가져오지 않는다. 이전 체크포인트는 토큰 기준으로
읽혀 새 요약에 접힌다. 약한 압박에서 요약이 실패하면 이력을 그대로 둔다(예전에는 지웠다). 요약 요청 자체가 너무
길다고 거절되면 provider 수치로 대본을 줄여 다시 보내고 그 비율을 턴의 보정에 반영한다. 기준점이 없을 때(턴 첫
호출)는 대본을 창의 40% 로 잡는다.
- **백그라운드 요약** (상태를 유지하는 호스트): 도는 동안 표시(`state.shared` 의 시각 값, 복사 · 직렬화 가능)를 남겨 같은
이력을 다시 요약하지 않고, 결과를 적용할 때 고정한 사용자 말도 함께 옮긴다. 실패는 그 요약을 건 턴에만 센다(이어
달리기 조각 포함). Stage 16 의 압축 요청은 도는 요약을 취소하고 바로 요약한다.
- **기억 기록**: 기록되기 전에 요약으로 빠진 메시지를 STM 기록 · 대화 아카이브가 먼저 기록한다
(`memory.compacted_unrecorded`, 기록기마다 최대 300개). 워터마크가 없던 기록기는 0 으로 본다.
- **도구 결과 상한**: 도구별 상한(기본 10만 자, 넘으면 4.88 과 같이 저장 후 미리보기)과 별개로, 모델이 읽는 글
(display_text 가 있으면 그것)이 입력 예산의 25% 토큰을 넘으면 앞뒤만 남긴다. 모든 도구에 적용한다(ToolBatch 포함).
글자가 아니라 토큰으로 재고(영어 6만 자는 200k 창에서 그대로 간다), 결과 속 이미지는 남기며, 구조화 오류는
`ERROR <code>` 첫 줄을 지킨다. 실패해도 결과를 그대로 돌려준다.
- **호스트 입력 맞춤** (`fit_input_to_budget`): guard 와 같은 답 몫을 남기고, 맞춤 뒤에 Stage 3 이 붙이는 숨긴 도구 목록 ·
이번 턴 안내 · 날짜도 센다. API 백엔드는 이력을 체크포인트가 차지할 수 있는 최대 크기만큼만 세어, 압축할 수 있는 지난
대화 때문에 이번 질문을 자르지 않는다. CLI 백엔드(claude_code · codex)도 CLI 창(claude_code 200k, `[1m]` 별칭은 1M,
codex 272k, claude_code 는 CLI 의 답 몫과 도구 몫을 더 남김)으로 맞추고, 넘치면 가장 오래된 이력부터 뺀다. 도구는 실제로
보내는 것(core + 열린 도구)만 센다. "입력 일부를 생략" 안내는 사용자 입력(질문 · 참조)이 잘렸을 때만 나간다.
- **메모리 노드 문맥**: DB 메모리 노드가 `(최근 메시지, 이전 대화 문자열)` 로 보낸 둘째 칸을 붙인다(`history_context`).
예전에는 버려져 최근 2개 메시지만 모델에 갔다. API 백엔드는 시스템 프롬프트 끝(옛 agent_xgen 과 같은 자리, 한 턴의 여러
호출에서 접두 캐시에 든다), CLI 백엔드는 이번 턴 안내(시스템 프롬프트가 argv 로 가서 매 턴 바뀌면 예열 프로세스를 못
쓴다). 창의 25% 안으로 줄이고, 입력 맞춤에서 질문 · 참조보다 먼저 줄인다.
- **내장 메모리 주입**: 주입 상한(기본 10,000자)을 창의 10% 로 묶고, 입력 맞춤의 예약도 같은 값에서 계산한다.
자동 검색 층은 압축 기록(`compactions`)을 올리지 않고, 그만큼 조금 더 받아 온다.
- **창 표**: Claude Opus 4.6 이후 · Sonnet 4.6 이후 · Haiku 5.5 · Fable · Mythos 1M, gpt-5 계열 272k(입력 한도),
gpt-5-chat 128k, gpt-5.5 · gpt-6 922k, Gemma(4 · 3 은 128k, 3n · 1b · 270m 은 32k, 2 는 8k), Azure 기본 128k.
Bedrock 은 이 런타임에서 Claude 만 부르므로 기본 200k 그대로다. vLLM 은 표를 쓰지 않는다(같은 모델도 창을 줄여 띄우는
일이 흔하다: 서버 실측, 카탈로그, 기본 32k 순). 접두는 이름 앞이나 구분자(. / :) 뒤에서만, 숫자를 이어 가지 않을
때만 맞춘다('gpt-5.5' 는 'gpt-5.50' 에 맞지 않는다).
- **Claude Code argv**: 약 100KB(Windows 는 명령줄 한도 때문에 6,000자)를 넘는 시스템 프롬프트는
`--system-prompt-file` 로, 넘는 프롬프트는 stream-json stdin 으로 보낸다. 파일은 사용자 전용 폴더에 0600 으로 쓰고
(POSIX 는 폴더의 주인과 권한을 확인하고, 이름을 남이 쓰고 있으면 프로세스 전용 임시 폴더로 바꾼다) 하루 지나면 지운다.
- **`/compact`**: 상태에 수동 압축 요청을 남기고 Stage 2 가 다음 패스에서 문턱과 무관하게 요약한다(예전 명령은 아무
파이프라인도 등록하지 않는 전략을 찾아 늘 실패했다). 압축이 꺼진 에이전트에서는 실패로 답한다.
- 새 사건: `context.overflow` · `context.compaction_skipped` · `context.compaction_deferred`.
바뀐 것이 없으면 `context.compacted` 대신 `context.compaction_skipped`(reason `no_change`)를 낸다.

## [4.88.0] - 2026-10-11

### Added — 호스트가 이번 턴 안내를 턴 노트로 넘기는 선택 훅 `turn_notes()`
Expand Down
53 changes: 47 additions & 6 deletions docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<!-- AUTO-GENERATED — do not edit by hand. -->
<!-- Regenerate: python scripts/gen_event_docs.py -->

> Generated from `xgen_agent_runtime.events.catalog` on 2026-10-11.
> Catalogue version: **17** · events: **140**
> Generated from `xgen_agent_runtime.events.catalog` on 2026-10-12.
> Catalogue version: **17** · events: **143**

Every event name the engine emits, value == wire string. The enum
is a *names registry*, not a rename — consumers matching raw strings
Expand Down Expand Up @@ -297,11 +297,14 @@ Enum member: `EventTypes.CONTEXT_COMPACTED`

| Field | Description |
|---|---|
| `strategy` | str — compactor name/class |
| `trigger` | str? — proactive \| requested \| guard \| internal_loop \| background |
| `strategy` | str — compactor name/class, or 'prune' when the deterministic pass alone met the target |
| `trigger` | str? — proactive \| requested \| manual \| guard \| overflow \| internal_loop \| background |
| `messages_before` | int? |
| `messages_after` | int? |
| `saved_tokens_estimate` | int? |
| `tokens_after_estimate` | int? — projected prompt tokens after the relief |
| `target_tokens` | int? — where the relief aimed (50% of the window by default) |
| `target_met` | bool? |

### `context.pruned`

Expand All @@ -310,10 +313,14 @@ Enum member: `EventTypes.CONTEXT_PRUNED`
| Field | Description |
|---|---|
| `deduped` | int — duplicate tool results rewritten to a back-reference |
| `images_stripped` | int — stale base64 images replaced with a marker |
| `images_stripped` | int — stale base64 images/documents replaced with a marker |
| `trimmed` | int — oversized stale tool results shortened |
| `inputs_trimmed` | int — oversized stale tool-call arguments shortened |
| `chars_saved` | int — text chars removed (images excluded) |
| `trigger` | str? — cost (Stage 2 token threshold) \| absent when run inside compaction |
| `aggressive` | bool? — the hard-pressure profile (guard / overflow) |
| `results_fitted` | int? — tool results shortened in proportion so a hard-pressure request fits |
| `current_attachments_stripped` | int? — images/documents of the request itself removed (the very last resort) |
| `trigger` | str? — cost (Stage 2 token threshold) \| the compaction trigger it ran under |
| `threshold_tokens` | int? — the prune_over_tokens threshold that fired (cost trigger) |
| `tokens_before` | int? — projected prompt tokens before the pass (cost trigger) |
| `tokens_after` | int? — projected prompt tokens after the pass (cost trigger) |
Expand Down Expand Up @@ -375,6 +382,40 @@ Enum member: `EventTypes.CONTEXT_COMPACTION_TARGET_MISSED`
| `tokens_after_estimate` | int |
| `target_tokens` | int |

### `context.compaction_skipped`

Enum member: `EventTypes.CONTEXT_COMPACTION_SKIPPED`

| Field | Description |
|---|---|
| `strategy` | str — compactor name/class |
| `trigger` | str |
| `reason` | str — turn_compaction_limit \| summary_model_failing \| already_summarized_for_this_request \| background_summary_running \| nothing_to_summarize \| summary_region_too_small \| no_change |
| `tokens_estimate` | int? |
| `target_tokens` | int |

### `context.compaction_deferred`

Enum member: `EventTypes.CONTEXT_COMPACTION_DEFERRED`

| Field | Description |
|---|---|
| `compactor` | str |
| `reason` | str — why no summary was produced (soft pressure leaves the history unchanged) |
| `attempts` | int — model calls tried |

### `context.overflow`

Enum member: `EventTypes.CONTEXT_OVERFLOW`

| Field | Description |
|---|---|
| `message` | str — the provider's error, first 500 chars |
| `reported_tokens` | int — prompt size the provider named, 0 when it named none |
| `estimated_tokens` | int — our estimate for the rejected request |
| `window` | int — the window we believed |
| `provider_limit` | int — window the provider named, 0 when none (used for the rest of the turn when smaller) |

### `context.short_term_window`

Enum member: `EventTypes.CONTEXT_SHORT_TERM_WINDOW`
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "xgen-agent-runtime"
version = "4.88.0"
version = "4.89.0"
description = "Harness-engineered agent pipeline library with 21-stage dual-abstraction architecture, built on the Anthropic API"
readme = "README.md"
license = "Apache-2.0"
Expand Down
Loading
Loading