글

npm stage-only 토큰이 바꾸는 배포 경계: 자동화는 제출하고 사람은 승인한다

이미지
npm이 2026년 9월 18일 패키지 버전을 바로 배포하지 못하는 stage-only granular access token을 공개했습니다. 자동화가 버전을 만들고 검증하는 일은 계속할 수 있지만, 공개 레지스트리에 올리는 마지막 동작은 별도 승인으로 남겨 두는 방식입니다. 이 기능은 새 인증 방식 하나가 추가됐다는 소식보다 배포 파이프라인의 권한을 나누는 방법으로 보는 편이 정확합니다. CI가 가진 토큰이 탈취돼도 곧바로 새 버전을 공개하지 못하게 만들고, 유지 관리자가 2FA를 거쳐 마지막 결정을 내리게 합니다. 다만 기존 토큰의 권한을 자동으로 바꾸는 기능은 아니며, opt-in 전환입니다. stage와 publish는 다른 동작이다 일반적인 npm 흐름은 npm publish 한 번으로 패키지를 레지스트리에 공개합니다. stage-only 토큰을 쓰면 자동화는 npm stage publish 로 버전을 staging area에 제출합니다. 이 제출은 2FA 없이 가능하지만 공개 배포는 아닙니다. 유지 관리자가 CLI나 npmjs.com에서 내용을 확인하고 2FA로 승인해야 실제 버전이 공개됩니다. 실무 설계에서는 이 구분에 맞춰 배포 성공을 두 단계로 나눌 수 있습니다. 첫 단계는 빌드 산출물, tarball, 의존성, 가능한 경우 provenance를 검사하고 제출하는 일입니다. 이는 운영상 권고이지 stage-only 토큰 자체가 검사를 자동 수행한다는 뜻은 아닙니다. 두 번째 단계는 사람이 게시 대상을 확인하고 승인하는 일입니다. CI 로그에 "stage succeeded"가 남았다고 해서 사용자가 즉시 설치할 수 있는 버전이 생긴 것은 아닙니다. 그림 1. 자동화는 npm stage publish로 제출하고, 유지 관리자가 검토·2FA 승인한 뒤 공개한다. 출처: docs.npmjs.com/staged-publishing . 공식 문서는 staged publishing의 순서를 stage, review, app...

하네스를 고치는 모델과 실제로 이득을 보는 모델은 다르다

이미지
에이전트가 실패 기록을 읽고 프롬프트, 스킬, 메모리, 도구 설명을 스스로 고쳤다고 하자. 다음 실행의 성능이 오르면 "좋은 모델이 좋은 하네스를 만들었다"고 말하기 쉽다. 하지만 여기에는 서로 다른 두 능력이 섞여 있다. 하나는 실행 증거에서 쓸 만한 규칙을 만들어내는 능력이고, 다른 하나는 그 규칙을 실제 작업 중 불러와 끝까지 따르는 능력이다. arXiv 프리프린트 Harness Updating Is Not Harness Benefit 는 이 둘을 harness-updating 과 harness-benefit 으로 분리했다. 연구진은 일곱 LLM을 SWE, MCP-Atlas, SkillsBench 세 벤치마크에서 업데이트 작성자와 작업 수행자로 교차 배치했다. 결과는 직관과 달랐다. 비싼 모델이 항상 더 좋은 업데이트를 만드는 것도 아니었고, 약한 모델이 외부 스킬만 받으면 곧바로 강해지는 것도 아니었다. 업데이트를 잘 쓰는 능력은 모델 크기와 평평했다 업데이트 작성자, 즉 evolver를 바꾸었을 때 세 벤치마크에서 가장 좋은 모델과 가장 나쁜 모델의 향상 폭 차이는 최대 3.1%포인트였다. 어느 한 모델이 모든 벤치마크에서 이기지도 않았다. 가장 작은 작성자였던 Qwen3.5-9B는 SkillsBench에서 3.8%포인트 향상을 냈다. 논문이 보고한 Opus 4.6의 2.3%포인트보다 높고 Qwen3-235B의 1.5%포인트보다도 높다. 이 수치가 "9B 모델이 항상 Opus보다 낫다"는 뜻은 아니다. 연구의 핵심은 작성자 모델의 기본 작업 능력과 하네스 업데이트의 효용이 강하게 함께 움직이지 않았다는 점이다. flink-query 사례에서는 9B와 Opus가 표현은 달라도 사실상 같은 절차를 담은 스킬을 작성했고, 둘 다 동일한 작업 모델의 점수를 0.67에서 1.0으로 올렸다. 그림 1. 하네스 업데이트 생성과 실제 활용은 별개의 능력이다. SkillsBench에서 Qwen3-32B와 Opus 4.6의...

GitHub Actions 실행 보호가 GA가 됐다: pull_request_target을 막기 전에 볼 세 경계

이미지
GitHub가 2026년 9월 17일 Actions의 workflow execution protections를 정식 출시했습니다. 이름만 보면 워크플로 안의 if: 조건을 하나 더 두는 기능처럼 보이지만, 적용 지점이 다릅니다. 워크플로가 runner에 올라가기 전에 누가(actor) 어떤 이벤트(event)로 실행을 시작할 수 있는지 정책으로 거릅니다. 이번 정식 버전에는 특정 workflow 파일만 겨냥하는 조건, 정책 평가 결과를 모아 보는 Insights, enterprise·organization·repository 수준의 REST API가 추가됐습니다. 공개 저장소의 pull_request_target 을 기본 차단하는 규칙도 함께 예고됐습니다. 이 기본 규칙은 처음에는 evaluate mode로 동작하고, 영향을 받는 저장소에는 2026년 11월 2일부터 강제 적용될 예정입니다. actor와 event를 모두 통과해야 실행된다 실행 보호는 허용 목록 방식입니다. actor 규칙은 실행을 요청한 주체를, event 규칙은 push , workflow_dispatch , pull_request_target 같은 시작 이벤트를 봅니다. GitHub는 두 조건을 모두 평가한 뒤 run을 만들지 결정합니다. 허용되지 않은 실행은 runner 안에서 실패하는 것이 아니라 정책 오류와 함께 실행 단계에서 막힙니다. 정책은 enterprise, organization, repository에 각각 둘 수 있습니다. 상위 정책을 하위 관리자가 느슨하게 덮는 구조로 이해하면 위험합니다. 공식 문서는 여러 범위의 보호가 계층적으로 함께 적용된다고 설명합니다. enterprise에서 양보할 수 없는 넓은 제한을 두고, organization과 repository에서 더 좁은 제한을 추가하는 편이 맞습니다. 그림 1. 실행 보호는 actor와 event를 runner 앞에서 평가하고, enterprise·organization·repository 및 workf...

작은 모델이 데이터베이스 에이전트에서 막힌 이유: 실패 75.7%는 도구 호출 뒤였다

이미지
작은 로컬 언어 모델이 에이전트 작업에 실패하면 흔히 모델 크기부터 의심합니다. 추론 능력이 부족해서 tool loop를 이어 가지 못했다고 보는 겁니다. 2026년 9월 18일 공개된 What Stops a Small Language Model From Driving a Database Agent 는 그 설명을 실제 제품 로그와 대조했습니다. 연구 대상은 오픈소스 SQL 클라이언트 LibreDB Studio의 read-only 데이터베이스 에이전트입니다. 연구진은 11일 동안 로컬에서 제공한 open-weight 모델 39개와 hosted control 1개를 여섯 가지 작업 표면에서 실행했습니다. 전체 기록은 8,199회 실행, 110,711개 ledger event, 거절된 tool call 14,008개입니다. 결과는 "작은 모델은 도구를 부르지 못한다"는 단순한 그림과 달랐습니다. 모델에 귀속한 agent-mode 실패 2,100건 중 1,590건, 75.7%는 적어도 한 번 도구를 호출한 뒤에 일어났습니다. 모델은 움직였지만 모델과 서버 사이의 경계에서 결과를 잃는 경우가 더 많았습니다. 실패 2,100건을 네 가지로 나눴다 논문은 실패를 capability, transport, clock, verification 네 범주로 분리했습니다. capability는 도구를 한 번도 호출하지 못한 경우입니다. transport는 도구를 썼지만 최종 산출물을 서버까지 전달하지 못한 경우, clock은 시간 제한에 걸린 경우, verification은 증거 요건을 충족하지 못한 경우입니다. 연구진이 채택한 clock-first 분류에서 transport는 761건(36.2%), clock은 542건(25.8%), verification은 434건(20.7%), capability는 363건(17.3%)이었습니다. 그림 1. 실패의 75.7%는 이미 도구를 호출한 실행에서 발생했다. 범주 순서는 이 corpus의 결과이며 모델 일반 ...

GIL을 끄면 모든 스레드가 빨라질까: Python 3.14 free-threaded 빌드의 세 경계

이미지
"Python에서 GIL을 없앴다"는 문장만 보면 이제 threading 으로 CPU 코어를 모두 쓰면 될 것 같다. Python 3.14의 free-threaded CPython은 실제로 여러 스레드가 서로 다른 코어에서 Python 코드를 병렬 실행할 수 있다. 다만 설치만 바꾸면 기존 코드가 자동으로 빨라진다는 뜻은 아니다. 공식 문서를 따라가면 확인할 대상이 세 층으로 나뉜다. free-threading을 지원하는 빌드인지, 현재 프로세스에서 GIL이 꺼져 있는지, 불러온 C 확장이 그 상태를 유지하는지다. 여기에 공유 상태의 동기화와 단일 스레드 비용까지 더해야 운영 판단이 가능해진다. Python 3.14는 기본 전환이 아니라 지원 단계다 PEP 779 는 free-threaded CPython의 전환을 세 단계로 설명한다. Python 3.13의 1단계는 실험적 빌드 제공, 3.14가 목표로 삼은 2단계는 공식 지원이지만 선택 사항인 빌드다. GIL 없는 빌드를 기본값으로 만드는 3단계는 별도 결정으로 남겨 뒀다. 이 구분은 배포 방식에 직접 영향을 준다. 일반 CPython을 설치한 뒤 설정 하나로 구현 전체가 바뀌는 구조가 아니다. 먼저 free-threaded 빌드를 선택해야 한다. python -VV 와 sys.version 에는 free-threading build 가 표시되고, 빌드 설정은 sysconfig.get_config_var("Py_GIL_DISABLED") 로 확인할 수 있다. 하지만 빌드가 지원한다고 현재 프로세스의 GIL이 꺼졌다고 단정할 수는 없다. PYTHON_GIL 이나 -X gil 옵션으로 다시 켤 수 있고, 실제 런타임 상태는 sys._is_gil_enabled() 로 봐야 한다. 그림 1. 빌드가 free-threading을 지원해도 런타임 옵션이나 지원 표시가 없는 C 확장 때문에 GIL이 켜질 수 있다. 출처: docs.python.org/3/howto/free...

3,100개 궤적으로 본 장기 에이전트의 붕괴 지점

이미지
짧은 작업을 잘하는 에이전트가 긴 작업도 비슷하게 잘할 거라고 기대하기 쉽다. 현실은 그렇게 매끈하지 않다. 한 단계의 작은 실수가 다음 단계의 전제가 되고, 초반에 받은 제약은 뒤로 밀리며, 마지막에는 실패 원인조차 찾기 어려운 궤적이 남는다. HORIZON 논문은 이 문제를 웹 탐색, 운영체제, 데이터베이스, 로봇 조작이라는 네 영역에서 비교했다. 연구진은 700개가 넘는 과제에서 GPT-5-mini와 Claude-4-Sonnet 계열을 실행해 3,100개가 넘는 궤적을 모았다. 핵심은 단순히 "몇 번 행동했나"가 아니라, 과제를 끝내려면 원래 몇 번의 유효 행동과 몇 겹의 하위 목표가 필요한지를 따로 본 데 있다. 50번 움직였다고 긴 과제는 아니다 에이전트가 같은 실패를 50번 반복해도 과제 자체는 짧을 수 있다. 논문은 이를 구분하려고 두 척도를 쓴다. H* 는 최적 정책이 과제를 끝내는 데 필요한 최소 유효 행동 수다. s 는 중첩된 하위 목표나 조건 분기의 깊이다. 연구진은 s 를 늘리는 두 방법을 사용했다. 깊이 확장은 건너뛸 수 없는 중간 절차를 추가한다. 폭 확장은 여러 독립 과제를 한 워크플로로 묶고 전환·검증·상태 추적 비용을 더한다. 이 구분 덕분에 에이전트가 헤매서 길어진 기록과, 애초에 긴 의존 사슬을 가진 과제를 섞지 않는다. 그림 1. HORIZON의 과제 길이 측정, 통제된 확장, 전이 구간 탐색, 일곱 실패 범주 진단 흐름. 하나의 보편 임계값을 주장하지 않는다. 출처: arxiv.org/html/2604.11978v1 . 하나의 붕괴 숫자는 없었다 네 영역 모두 구성 깊이가 커질수록 성공률이 비선형으로 떨어졌다. 완만하게 낮아지다가 어느 구간에서 급격히 무너지는 모양이었다. 하지만 무너지는 위치는 같지 않았다. 웹과 로봇 조작은 작은 확장에도 빠르게 약해졌고, 운영체제와 데이터베이스는 이 실험에서 조금 더 긴 구간까지 버텼다. 따라서 "에이전트는 20단계부터 실패한다" 같은...

Python 3.15의 lazy import, Ruff 0.16.8이 TYPE_CHECKING 대신 고른 이유

이미지
Python에서 타입 힌트 때문에만 필요한 모듈은 흔히 if TYPE_CHECKING: 안으로 옮깁니다. 실행할 때는 import 비용을 내지 않는 대신, 그 이름도 런타임에는 존재하지 않습니다. Pydantic이나 SQLAlchemy처럼 어노테이션을 실행 중에 읽는 프레임워크를 만나면 이 차이가 바로 드러납니다. 현재 3.15.0rc2 문서에는 다른 선택지가 들어 있습니다. lazy import 는 이름을 런타임에 남겨 두되 실제 모듈 로딩과 실행은 첫 사용까지 미룹니다. 2026년 9월 16일 나온 Ruff 0.16.8은 이 새 문법을 TC001 , TC002 , TC003 수정안에 반영했습니다. 대상 버전이 Python 3.15 이상이고 형태가 단순하다면 타입 전용 import를 TYPE_CHECKING 블록으로 옮기기보다 lazy 를 붙이는 쪽을 먼저 제안합니다. import 문은 실행되지만 모듈은 아직 실행되지 않는다 보통의 import는 해당 줄에서 모듈을 찾고 로드한 뒤 최상위 코드를 실행합니다. lazy import는 그 자리에 프록시를 이름에 바인딩합니다. 모듈은 아직 sys.modules 에 들어가지 않습니다. 코드가 그 이름을 처음 사용할 때 모듈을 로드하고, 그 시점에 실제 객체로 바뀝니다. 그림 1. lazy import는 모듈 로딩 비용과 import 오류를 없애지 않고 첫 사용 지점으로 옮긴다. 출처: docs.python.org/3.15/reference/simple_stmts.html#lazy-imports . 이 차이는 시작 시간을 줄일 수 있지만 작업을 없애는 것은 아닙니다. 비용과 실패 시점을 뒤로 옮깁니다. 모듈에 ImportError 나 SyntaxError 가 있으면 import 줄이 아니라 첫 사용 지점에서 예외가 납니다. 프로그램 시작 직후 모든 선택 의존성을 확인하던 테스트라면 lazy import 뒤에도 같은 보장이 남는지 따로 검사해야 합니다. PEP 810은 이 기능을 전역 기본값으로 만들지...