메뉴
HN
Hacker News • 15일 전

아스트라 코딩 에이전트, 우리는 왜 또 이 짓을 하고 있나?

IMP
7/10
핵심 요약

파이썬의 창시자 중 한 명이자 CPython 개발자인 아르민 로나처가 GPT-6 아스트라로 주말 내내 '소프트웨어 공장' 실험을 돌려본 결과를 공유했습니다. 약 40억 토큰과 35시간을 소모했지만 가치 있는 결과물은 없었고, 모델이 장기 과제 완수에는 강하지만 저품질 코드에 대한 페널티가 부족해 엉뚱한 방식으로 작동한다고 분석했습니다. AI 엔지니어링 전반이 '내권(內卷, 인볼루션)' — 노력은 늘지만 실질 생산성은 그대로인 상태 — 를 닮아가고 있다는 비판이 핵심입니다.

번역된 본문

아르민 로나처의 생각과 글쓰기 블로그 아카이브, 프로젝트, 여행, 강연, 소개 — 「아스트라 코딩 에이전트: 우리는 왜 또 이 짓을 하고 있나?」, 2026년 9월 7일 작성.

저는 점점 더 AI 엔지니어링 전반이 '내권(内卷, Neijuan, 안으로 말려 들어간다는 뜻)'이라고 확신하게 되고 있습니다. 중국에서 이 말은 산출물을 개선하지 않으면서 끝없이 더 많은 노력과 경쟁을 요구하는 시스템을 가리킵니다. 서구에서 이것이 나타나는 형태가 바로 그 996 근무제 같은 헛소리입니다. 내권에 해당하는 영어 용어는 저서 『농업적 인볼루션(Agricultural Involution)』에서 나온 "Involution(인볼루션)"입니다. 농업적 인볼루션은 제곱미터당 생산성은 높이지만 1인당 생산성은 그대로인 농사의 집약화를 묘사합니다. 지금 AI가 저에게는 꼭 그렇게 느껴집니다.

그래서 GPT-6 아스트라 이야기로 넘어가 보겠습니다. 아스트라는 누가 뭐래도 믿을 수 없을 만큼 인상적인 모델입니다. 이 점에서 반박할 말이 별로 없습니다. 컴퓨터 사용에 뛰어나고, 이미지와 복잡한 주제를 이해하며, 완수를 향한 추진력이 멈추지 않습니다. 정말 놀랍습니다. 이런 종류의 모델은 어떤 형태로든 세상을 바꿀 것입니다. 하지만 적어도 지금으로서는 실제 소프트웨어 엔지니어링에 이것을 어떻게 활용해야 할지 모르겠습니다. 이 주제가 트위터에서 꽤 주목을 받았기에, 제 생각을 정리하고 이 모델이 어떤 코드를 뱉어내는지 공유해보려 합니다.

제 저질 양산 공장(Slop Factory)

"아르민, 소프트웨어 공장을 돌려봐야지!" 이런 말을 몇 번 들었기에, 출시를 기념하는 의미에서 주말 동안 작은 소프트웨어 공장을 돌려보기로 했습니다. 모두가 저질(slop) 3D 게임을 만든다면, 저는 뭔가 유용한 걸 해야죠. 제 소프트웨어 공장은 워크플로의 '방법'을 모델이 전적으로 결정하도록 의도적으로 설정했습니다. 모델은 자신의 컨텍스트를 스스로 관리할 수 있었고, agent-notes 폴더에 자기 기록을 유지할 수 있었습니다. 그리고 작업을 수행할 하위 에이전트(subagent)들을 파생시켰습니다. 목표는? 가상 스레드와 렉시컬 스코프을 갖춘 파이썬이 있다면 어떨까 하는 것이었습니다.

그리고 이 작업에 ChatGPT 토큰을 1회 리셋분 전체 — 대략 40억 토큰쯤 되는 것 같습니다 — 를 태웠습니다. 35시간 후, 공장은 가치 있는 것을 단 하나도 만들어내지 못했고, 더 나은 공장을 운영하는 방법에 대해서도 아무것도 가르쳐주지 않았습니다. 하지만 코드와 입력 프롬프트는 많이 만들어냈기에 연구해볼 거리는 있었습니다. 그런데 솔(Sol)이나 이전 OpenAI 모델들에서는 보지 못했던 방식의 행동이 나타났습니다. 저는 이후 일반적인 프로그래밍에서도 아스트라로 같은 문제를 겪었기 때문에, 이건 공장 실험 때문만은 아닙니다. 학습 과정에서 뭔가 '잘못'되고 있다고 의심하고 있습니다. 이 모델은 장기 과제(long-horizon task)를 성공적으로 완수하면 크게 보상받지만, '엉터리 코드'에 대해서는 거의 처벌받지 않는 듯합니다. 그 결과, 아스트라는 3D 작업물을 만드는 데는 놀라울 정도로 뛰어나고 아주 오랫동안 작업을 지속하며 그 과정에서 스스로 일감을 만들어냅니다. 제 로봇 청소기를 꽤 인상적인 방식으로 리버스 엔지니어링하게 한 적도 있습니다. 그러니 분명 멋있긴 합니다!

코드골프식 도구 호출

아스트라에 대해 제가 갖는 첫 번째 문제는 도구 호출에 사용하는 코드의 유형에서 나옵니다. Codex는 점점 더 많은 작업을 '그냥 bash'로 처리하는 쪽으로 기울어왔습니다. 최근 몇 버전 동안 원래의 Codex 하니스는 파일을 읽을 때 그냥 sed 같은 도구를 사용합니다. Codex가 bash 명령을 파싱해서 인식된 것은 숨겨주기 때문에 보통 눈에 보이지 않을 뿐입니다. 그런데 아스트라는… 정말 파이썬을 좋아합니다? 그리 놀라운 일은 아닙니다. 예전 OpenAI 모델들도 때때로 파일을 읽고 조작하는 데 즉석 파이썬 코드를 쓰는 경향이 있었으니까요. 하지만 아스트라는 제 기준으로는 정말 과도하게 그렇습니다.

여기서 중요한 면책: 이 프로젝트는 저희가 CPython 인터프리터를 다루는 아주 '메타'한 작업이었습니다. 하지만 이 모델이 Pi의 타입스크립트 코드에서조차 이상한 파이썬 짓을 하는 걸 직접 봤다고 말씀드릴 수 있습니다. 다만 제가 가장 많은 증거를 확보한 것은, 감독 제로로 주말 내내 돌린 저의 저질 양산 공장에서였습니다. 파이썬을 쓴다는 것 자체는 흥미롭지 않습니다. 문제는 그 파이썬의 유형입니다…

원문 보기
원문 보기 (영어)
Armin Ronacher 's Thoughts and Writings blog archive projects travel talks about Astra for Coding: Why Are We Doing This Again? written on September 07, 2026 I'm more and more convinced that all of AI engineering is Neijuan (内卷, meaning curl inwards). In China it describes a system that demands ever more effort and competition without improving output. The way in which it sometimes shows up in the West is the 996 nonsense . The English term for Neijuan is "Involution" from the book Agricultural Involution . Agricultural involution describes the intensification of farming that raises productivity per square meter while leaving productivity per head unchanged. That's how I feel about AI right now. Which brings me to GPT 6 Astra. Astra is by all accounts an incredibly impressive model. There is really not much I can say against this. It's amazing at computer use, understands images and complex topics, and it's relentless in its pursuit of completion. It is absolutely impressive; these types of models are going to change the world in one form or another. But at least for the moment I don't know how to work with it for actual software engineering. Since that got quite a bit of attention on Twitter, I figured I might summarize my thoughts and just share what kind of code comes out of this thing. My Slop Factory "Armin, you should run a software factory!" I've heard that a few times now, so I figured I might celebrate the release of it by running a little software factory over the weekend. If everybody builds slop 3D games, then I should do something useful with it. My software factory was intentionally set up to let the model decide the how of the workflow entirely. It was free to manage its own context and could maintain its own records in an agent-notes folder. Then it spun off subagents to work on stuff. The goal? What if we had a Python with virtual threads and lexical scoping. And well, I burned a full reset's worth of ChatGPT tokens on this which appears to be around 4 billion tokens. 35 hours later, the factory has delivered absolutely nothing of value and also not taught me anything about how to operate a better one. But it produced a lot of code and input prompts, and so there is stuff I was able to study. And well, it shows behavior that I'm not used to with Sol and earlier OpenAI models 1 . I have since encountered the same issues with regular programming with Astra, so it's not a result of just the factory. I think I'm suspecting something is going "wrong" in the training process. The model is greatly rewarded for succeeding on long-horizon tasks, but presumably there is very little punishing going on for "shitty code." The apparent result is that Astra is amazing at producing 3D stuff and it can keep going for a very long time, coming up with its own work in the process. I had it do quite a bit of reverse engineering of my robot vacuum in ways that were quite impressive. So it's definitely cool! Codegolf Tool Calls The first issue I have with Astra comes from the type of code that it uses for tool calls. Codex increasingly has been relying on "just bash" to do more and more operations. For a few versions now the original Codex harness just uses sed and other tools to read files. You just usually can't see them because Codex parses the bash commands and hides them if it recognizes them. But Astra … really loves Python? That is not much of a surprise because even older OpenAI models had a tendency to sometimes use on-demand Python code to read and manipulate files at times, but Astra does it really quite excessively for me. Now here is an important disclaimer: this project is very meta here because I worked on the CPython interpreter. But I can assure you that I have seen this model do weird Python things even in TypeScript code in Pi. But I have the most evidence of odd code from when I had the thing work over the weekend with zero oversight from my slop factory. That it writes Python is not interesting; the type of Python is interesting, and I collected some outputs for you to gloss over. Python string splicing to edit C code In the Codex harness I found multiple cases where subagents resorted fully to manual string manipulation with Python instead of using the patch tool. python3 - << 'PY' from pathlib import Path p = Path ( 'Include/internal/pycore_intrinsics.h' ); s = p . read_text () . replace ( '#define MAX_INTRINSIC_1 14' , '#define INTRINSIC_RETAIN_ANNOTATION_CELLS 15 \n\n #define MAX_INTRINSIC_1 15' ); p . write_text ( s ) p = Path ( 'Python/intrinsics.c' ); s = p . read_text (); idx = s . index ( '#define INTRINSIC_FUNC_ENTRY' ); s = s [: idx ] + '''/* Hold every old cell until the compiler has published the entire site's new capture. A replaced cell's finalizer may reenter module __annotate__. */ static PyObject * retain_annotation_cells(PyThreadState *tstate, PyObject *holders) { if (!PyTuple_CheckExact(holders)) { PyErr_SetString(PyExc_TypeError, "annotation holders must be a tuple"); return NULL; } Py_ssize_t size = PyTuple_GET_SIZE(holders); PyObject *previous = PyTuple_New(size); if (previous == NULL) return NULL; for (Py_ssize_t i = 0; i < size; i++) { PyObject *holder = PyTuple_GET_ITEM(holders, i); if (!PyCell_Check(holder)) { Py_DECREF(previous); PyErr_SetString(PyExc_TypeError, "annotation holder must be a cell"); return NULL; } PyObject *cell = PyCell_Get(holder); PyTuple_SET_ITEM(previous, i, cell == NULL ? Py_NewRef(Py_None) : cell); } return previous; } ''' + s [ idx :]; s = s . replace ( ' INTRINSIC_FUNC_ENTRY(INTRINSIC_AWAIT_BLOCK, await_block)' , ' INTRINSIC_FUNC_ENTRY(INTRINSIC_AWAIT_BLOCK, await_block) \n INTRINSIC_FUNC_ENTRY(INTRINSIC_RETAIN_ANNOTATION_CELLS, retain_annotation_cells)' ); p . write_text ( s ) p = Path ( 'Python/codegen.c' ); s = p . read_text (); idx = s . index ( 'static int \n codegen_annassign(' ); s = s [: idx ] + '''static int codegen_retain_annotation_cells(compiler *c, location loc, PyObject *captures) { Py_ssize_t pos = 0; PyObject *binding, *holder; while (PyDict_Next(captures, &pos, &binding, &holder)) { ADDOP_NAME(c, loc, LOAD_CLOSURE, holder, cellvars); } ADDOP_I(c, loc, BUILD_TUPLE, PyDict_GET_SIZE(captures)); ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_RETAIN_ANNOTATION_CELLS); return SUCCESS; } ''' + s [ idx :] a = s . index ( ' if (conditional_annotation_index != NULL) {' , s . index ( 'codegen_annassign(compiler *c' )) b = s . index ( ' if (captures != NULL) {' , a ) # Move lookup before conditional registration and retain old cells before anything changes. lookupstart = s . index ( ' PyObject *captures = _PyCompile_AnnotationCaptures' , a ) lookup = s [ lookupstart : b ] . replace ( ' return ERROR;' , ' Py_XDECREF(conditional_annotation_index); return ERROR;' ) s = s [: lookupstart ] + s [ b :] setup = lookup + ''' if (captures != NULL && codegen_retain_annotation_cells(c, loc, captures) < 0) { Py_XDECREF(conditional_annotation_index); return ERROR; } ''' s = s [: a ] + setup + s [ a :] needle = ' ADDOP_NAME(c, loc, STORE_DEREF, holder, cellvars); \n } \n }' s = s . replace ( needle , ' ADDOP_NAME(c, loc, STORE_DEREF, holder, cellvars); \n } \n ADDOP(c, loc, POP_TOP); /* release old cells after full publication */ \n }' , 1 ); p . write_text ( s ) p = Path ( 'Include/internal/pycore_magic_number.h' ); s = p . read_text () . replace ( ' Python 3.16a1 3709 (Checked deferred annotation closure capture)' , ' Python 3.16a1 3709 (Checked deferred annotation closure capture) \n Python 3.16a1 3710 (Retain replaced annotation captures until publication)' ) . replace ( '#define PYC_MAGIC_NUMBER 3709' , '#define PYC_MAGIC_NUMBER 3710' ); p . write_text ( s ) p = Path ( 'Lib/test/test_block_annotation_captures.py' ); s = p . read_text (); idx = s . index ( ' def test_typing_consumers' ); s = s [: idx ] + ''' def test_replaced_cell_finalizer_sees_complete_site_publication(self): module=execute(""" \\ events=[] class V: def __init__(self,n): self.n=n def __del__(self): if self.n == 0: events.