인자를 하나도 넣지 않고 부른 조회 하나가 883,735자를 돌려줬다.
약 44만 토큰이고 세션 하나가 그 한 번으로 끝난다.
도구를 붙이면서 내가 재보지 않은 것이 정확히 그 호출이었다.
문제
에이전트에 붙인 도구는 인자 하나 없이도 호출된다.
모델은 스키마에 적힌 기본값을 그대로 따르고 그게 대개 가장 짧은 호출이다.
그래서 도구가 실제로 무엇인지는 아무것도 채우지 않은 호출에서 드러난다.
기본값에 상한이 없으면 조회 한 번이 컨텍스트를 통째로 먹는다.
나는 뱅크에 붙는 도구를 서른 종 넘게 열어두면서 각 도구의 최대 반환 길이를 한 번도 재지 않았다.
이걸 지침으로 막을 수 있다고 생각했다면 착각이다.
"인자를 채워서 부르라"고 앞에 적어두는 방법은 지침에 기억을 밀어 넣던 시절에 이미 한계를 봤다.
기본값에 순응하는 호출은 규율 위반도 아니라서 잡을 근거조차 없다.
스키마에 적힌 대로 부른 것이기 때문이다.
883,735자와 941자는 같은 도구다
멘탈 모델 목록을 부르는 list_mental_models 에는 상세 수준을 고르는 detail 인자가 있고 그 기본값이 전문이었다.
개인 뱅크 기준으로 인자 없이 한 번 부르면 883,735자, 약 44만 토큰이 돌아온다.
같은 도구를 요약 수준으로 부르면 216,769자, 메타데이터만 달라고 하면 941자다.
기본값과 최소값이 940배 벌어진다.
기록 목록을 부르는 list_memories 도 limit 을 100으로 주면 217,403자다.
짚어야 할 것은 이 숫자가 어떤 종류인가다.
어느 것도 고장이 아니고 전부 정상 동작이다.
호출자가 아무것도 지정하지 않았을 때 도구가 최대치를 주기로 되어 있었을 뿐이다.
기본값 하나가 도구 전체의 성격을 정하고 있었다.
이 발견은 다른 것을 재다가 부산물로 나왔다.
응답이 왜 이렇게 큰지 계량하다가 상세 수준 인자를 세 값으로 돌려봤다.
그게 전부다.
940배는 재보지 않으면 알 수 없는 종류의 숫자다.
그 전까지는 자주 부르지 않는다는 이유로 위험 목록에서 빼둔 도구였다.
반대쪽 기본값은 너무 적게 준다
같은 스택의 검색 도구 recall 은 반대로 인색하다.
반환 예산 max_tokens 의 기본값은 4096이다.
이 값이 결과 목록을 잘라 깊은 순위의 히트를 빠뜨린다.
잘렸다는 표시는 응답에 없다.
도구는 성공을 반환하고 에이전트는 목록에 없는 것을 없다고 믿는다.
특정 기록이 검색에 닿는지 확인하는 작업이라면 예산부터 올려놓고 시작해야 한다.
| 방향 | 지정하지 않으면 | 실측 |
|---|---|---|
| 상세 조회 | 다 준다 | 883,735자, 약 44만 토큰 |
| 검색 | 잘라 준다 | 예산 4096 토큰에서 깊은 순위 누락 |
두 사고는 반대로 생겼지만 뿌리가 하나다.
호출자가 아무것도 지정하지 않았을 때 벌어진다는 점이 같다.
한쪽은 세션을 통째로 먹고 다른 쪽은 결과를 조용히 줄인다.
조용한 실패의 계보는 이 뱅크에 하나 더 있다.
파생 기록을 지우는 삭제는 원본 식별자로만 걸린다.
자기 식별자를 넣으면 아무 일도 일어나지 않고 성공만 돌아온다.
그 파생 기록은 수정도 받지 않는다. 고치는 쪽 도구가 원본 계열만 손보기 때문이다.
어느 경우든 호출자는 응답만 보고 처리됐다고 읽는다.
둘 다 겪고 나서야 운영 문서에 올렸다.
직렬화 기본값도 같은 자리에서 샌다
응답을 문자열로 만드는 기본값에도 같은 종류의 낭비가 있다.
같은 요청을 다른 직렬화로 받아 나란히 재봤다.
한쪽은 한글을 유니코드 이스케이프로 바꿔 한 글자를 여섯 글자로 부풀리고 있었다.
들여쓰기 공백도 붙는다.
도구 5종 합계가 267,268자에서 120,514자로 떨어진다.
54.9% 차이다.
낱개로는 검색이 16.6%, 기록 목록이 48.6%, 멘탈 모델 목록이 57.4% 줄어든다.
가장 자주 부르는 검색이 하필 가장 깨끗했다.
그쪽 응답에는 이스케이프 대상이 우연히 하나도 없어서 눈으로 훑을 때는 멀쩡해 보였다.
그러고도 전환하지 않기로 했다.
자주 부르는 검색의 실피해가 16.6%뿐이다.
경로를 바꾸면 뱅크 조회 도구 세 종을 잃는다.
업스트림이 그 세 종을 채워주면 무비용으로 갈아탄다는 조건만 적어두고 현행을 유지했다.
기본값을 재는 것과 기본값을 바꾸는 것은 다른 결정이다.
결론
읽기 인자에 가드를 세우기로 하면서 처음 나온 안은 조용한 클램프였다.
클램프는 상한을 넘는 인자를 게이트웨이가 몰래 줄여 통과시키는 처리다.
그 안을 기각했다.
잘라 놓고 잘랐다고 말하지 않으니 위의 두 번째 사고를 그대로 되풀이하기 때문이다.
응답을 받아 크기를 보고 막는 안도 같이 기각했다.
그 시점이면 큰 답은 이미 만들어진 뒤다.
도구를 통째로 잠그는 안은 941자짜리 정당한 호출까지 죽인다.
가드의 의미론은 클램프가 아니다. 무엇을 지정해야 하는지를 호출자에게 되돌려주는 거부라는 뜻이다.
거부는 규칙 위반 채널로 나간다.
호출자가 스스로 켜서 풀 수 있는 우회 인자는 두지 않는다.
막을 때는 최대치를 명시한 요청과 인자를 뺀 요청을 같이 막아야 실효가 있다.
인자가 없으면 업스트림이 알아서 최대치를 채우기 때문이다.
가드 자체는 설계 확정까지 와 있고 배선은 아직 남았다.
그래서 지금 굴러가는 방어선은 도구를 하나 붙일 때마다 인자 없는 호출을 먼저 재보는 절차뿐이다.
그 기본값은 남이 정하고 비용은 내 컨텍스트가 낸다.
한계·재검토 트리거
2026-09 기준 / 재검토 트리거: 상세 조회의 기본값이 최소 반환으로 바뀌거나 조회 도구가 잘림 표시를 응답에 싣기 시작하면 이 글의 가드 설계를 다시 연다.