요약
에이전트가 도구를 코드로 엮어 부르는 Codemode에는 bash로 대신할 수 없는 쓸모가 있습니다.
개발자 아르민 로나허가 코딩 에이전트 Pi 1.0에 Codemode 방식의 MCP(에이전트에 도구를 붙이는 표준 규약) 지원이 들어간 것을 계기로 쓴 글입니다. bash만 있으면 되지 않느냐는 물음에, 하네스(모델과 도구를 잇는 실행 틀) 쪽에서 코드를 돌려야 하는 이유를 답합니다.
왜 중요한가
- 도구를 하나씩 부르지 않고 프로그램으로 묶으면 중간 결과가 컨텍스트 창(모델이 한 번에 읽는 입력)을 채우지 않습니다.
- bash는 도구를 실행하는 환경에서 돌지만 Codemode는 하네스 안에서 돌아서 하네스 고유 기능을 코드로 부를 수 있습니다.
- 글쓴이는 MCP 생태계도 결국 자신이 주장해 온 '코드 중심' 방향으로 움직이고 있다고 봤습니다.
핵심 내용
- Codemode 코드는 네트워크·파일·타이머가 없는 QuickJS(WASM) 샌드박스에서 돌고, 바깥으로 나가는 길은 도구 호출뿐입니다.
- 이미지를 모델에 넣거나 하위 에이전트를 띄우는 일은 bash로 할 수 없어 하네스가 직접 제공해야 합니다.
- 여러 작업을 동시에 돌리고, store()로 결과를 세션 기록에 남겨 다음 호출에서 다시 쓸 수 있습니다.
- 예시로 GitHub 이슈 100개를 분류기로 감정 분석해 불만이 가장 큰 12개를 고르는 작업을 보였습니다.
- 지금의 MCP 서버는 안에 자체 Codemode를 감싸 JSON이 이중으로 이스케이프되는 등 잘 맞지 않는다고 지적했습니다.
HN 반응
- 여러 이용자는 Codemode를 도구 호출을 엮는 작은 자바스크립트 샌드박스로 정리하며, 중간 결과를 컨텍스트에 싣지 않는 점을 핵심으로 꼽았습니다.
- 왜 자바스크립트냐며 bash로도 충분하다는 반론이 나왔고, 로나허는 bash가 하네스와 다른 곳에서 돌아 따로 통신 계층이 필요하다고 답했습니다.
클라우드플레어 글이나 이 글이나 말은 많은데 시스템 차원의 단순한 그림은 없습니다. 제가 보기에 실제로 벌어지는 일은 이렇습니다.
동도상성(homoiconicity): 하니스(harness)의 작동 방식은 누더기 같고, 코드(도구 호출)와 데이터("자연어")를 토큰 스트림으로 한결같이 다루려면 제대로 된 동도상성이 필요합니다.
액터 의미론: 격리, 캡슐화, 동시성을 위한 것입니다.
객체 능력(object capabilities): 참조를 주입하고 환경 권한(ambient authority)은 두지 않습니다. 능력 기반 리플렉션/인트로스펙션은 인터페이스와 어포던스를 알아내는 깔끔한 방법입니다.
"코드 모드"든 뭐든 결국 이걸 재발견하는 셈인데, 수십 년 쌓인 컴퓨터 과학의 시스템 설계 원칙에서 출발하는 게 아니라 LLM 토큰 스트림에서 바깥쪽으로 땜질해 나가는 방식입니다. LLM을 텍스트/토큰 생성기로, 하니스를 임시변통 인터프리터로 보는 대신, LLM을 프로그래밍 언어 런타임에 참여하는 주체로 대하기 시작한 것입니다(eval/apply를 해 보실 분 계신가요?).
기존 코드 모드 구현이 이걸 아직 다 지원하지 않는다면, 여기에 이를 때까지 계속 땜질을 쌓아 갈 거라고 예상합니다.
댄 잉걸스가 했던 말이 아마 이럴 겁니다. "운영체제란 언어에 들어맞지 않는 것들의 모음이다. 그런 건 없어야 한다." 하니스도 마찬가지로 봅니다. LLM에도, 프로그래밍 환경에도 끼지 못하는 어정쩡한 중간 자식이니까요.
답은 어쩌면 LLM을 Common Lisp나 Scheme 파이버, Spritely Goblins, 혹은 Erlang BEAM과 짝지어 놓고 끝내는 것일지도 모르겠습니다!
사실은 컨텍스트 관리 문제입니다. MCP 도구를 호출하면 잠재적으로 거대한 JSON이 컨텍스트 윈도에 올라가고, 압축(compaction)이 일어날 수도 있습니다. 서브에이전트를 쓰면 덜 나쁘긴 해도 여전히 비싸고요(게다가 모든 도구 호출을 서브에이전트로 돌리지는 않을 겁니다).
하지만 MCP 클라이언트 CLI가 있다면 jq 같은 데 파이프로 넘겨서 필요한 것만 뽑을 수 있습니다. 여러 도구 호출을 하나로 엮을 수도 있고요. 그러면 그 사이에 오가는 중간 텍스트는 에이전트가 전혀 받지 않습니다(다만 에이전트가 중간 결과를 임시 파일에 저장해 두고 싶을 수는 있겠지요).
그런데 셸 스크립트는 형편없습니다. JSON 같은 걸 다루기엔 Javascript나 Python이 더 알맞고, 이게 바로 codemode라고 부르는 것입니다.
그래서... 에이전트와 프로그래밍 언어를 통합하는 것은 아주 일리가 있습니다. 하지만 지금은 하니스끼리 어느 정도 서로 바꿔 쓸 수 있어서 다른 걸로 금세 갈아탈 수 있습니다. 이 움직이는 부품들 사이의 결합이 강해질수록 락인은 더 심하게 옵니다. (생태계에 심각하게 락인된 Claude Code가 얼마나 엉망인지 떠올려 보세요.)
사소한 지적입니다만, bash가 가장 우아한 언어는 아니어도 셸 언어에는 에이전트가 쓰기 쉽게 해 주는 아주 좋은 특징이 있습니다. 왼쪽에서 오른쪽으로, 토큰이 생성되는 순서 그대로 실행된다는 점입니다. Python/Javascript는 토큰 순서와 다르게 실행될 수 있어서(예: foo(bar(baz(bak(bat))))) 오류가 나기 더 쉽습니다. 게다가 셸은 매우 간결하고, 이것저것 이어 붙이라고 만든 언어입니다.
python/javascript를 셸처럼 쓰려고 애쓰기보다는 nushell 같은 쪽이 더 나은 방향일 것 같습니다.
어쩌면 셸 스크립트는 (에이전트에게는) 형편없지 않을지도 모릅니다. 사람에게는 오류가 나기 쉬워서 형편없지만요(사실 제가 가장 좋아하는 언어이긴 합니다). 제 짐작에는 Typescript 같은 걸 쓰면 컴파일러가 타입 오류 등을 잡아 주기 때문에 도구 호출 오류율이 눈에 띄게 낮아질 것 같습니다. 하지만 bash는 워낙 학습 분포 안(in distribution)에 있어서 그런 게 별 차이가 없을 수도 있고요.
이미 Python을 언급하셨으니 말인데요, 그건 해결할 수 있지 않나요?
그런 개념을 가지고 놀던 꽤 오래된 라이브러리들이 있어요. 특히 Python에는요.
https://pypi.org/project/plumbum/
대부분 동의하지만, 하니스에서 실행되는 코드와 에이전트가 bash 셸에서 실행하는 코드는 신뢰 수준이 다르다는 점도 덧붙이고 싶습니다.
저는 bash 호출의 네트워크 접근을 전부 막아 두었지만, 특정 서비스를 특정 방식으로만 호출하는 도구는 제공하고 싶습니다. 바로 그 일만 하면서 샌드박스 바깥에서 실행되는 도구(또는 mcp)를 만들면 됩니다.
맞습니다. 저희가 고객사 등에 권하는 보안 조치 중 단연 최고입니다. 단일 접근 지점을 훨씬 잘 통제할 수 있거든요.
동의합니다. 하니스는 빠르게 다음 시스템 프로그래밍의 최전선이 되고 있습니다. OS 비유는 들어맞고 자주 나오죠. 브라우저가 "플랫폼", "OS"가 되어 간다고 하던 것과 같습니다. 또 하나 뻔한 비유가 스레드/프로세스와 에이전트/서브에이전트입니다. 동시성, 공유 자원 접근, 협력적 멀티태스킹 같은 문제가 모조리 다시 떠오르고 있어요.
자리를 잡기까지는 시간이 걸리겠지만, 옛 아이디어들이 다시 새로워지고 있습니다. 저는 개인적으로 하니스를 설계하려고 리스프 머신, 동도상성, 액터 모델 같은 아이디어를 캐 보고 있습니다.
Autolith( https://autolith.rocks/ )를 비롯한 CL 기반 하니스 몇 가지는 LLM이 세션 안에서 자기 하니스를 직접 수정할 수 있게 해 줍니다.
여담인데요.
저는 이렇게 한 가지 언어에 집중하는 쪽이 앞으로 갈 길이라고 봅니다. 세상 온갖 언어를 다 AI에게 가르치는 대신, 아주 잘 이해하고 도구도 잘 갖춰진 언어 하나로 모든 문제를 푸는 모델이 나온다면 대단할 겁니다. 물론 모델 학습은 여러 언어에서 서로 이득을 얻으니, 그 이점이 나타나도록 따로 대책을 마련해야 하겠지만요.
저처럼 글과 댓글을 읽고도 "codemode"가 뭔지 여전히 모르겠다면 이렇습니다.
하니스에 작은 js 샌드박스를 줘서, 도구 호출을 엮고 그 결과 데이터를 컨텍스트로 읽어 들이기 전에 가공할 수 있게 하는 겁니다.
맞아요, codemode는 그냥 bash를 샌드박스에 넣은 JavaScript로 바꾸는 거예요.
말은 되는데, 왜 새 샌드박스를 만들죠? bun repl이나 다른 repl을 쓰면 안 되나요?
엉뚱한 문제를 푸는 해법 같은데요.
적어도 지금으로선 어느 정도 말이 돼요. 모델은 이미 (편집할 때조차) 걸핏하면 스크립트를 짜는 쪽으로 빠지는데, 도구용 스크립트를 짜게 하지 못할 것도 없잖아요.
그래도 여전히 좀 헛소리 같긴 해요. 모델 학습이 "제발 실수 없이 스크립트를 만들어 줘" 같은 마크다운 파일 따위를 다 덮어써 버리니까, 모델은 Codemod 같은 건 기꺼이 무시할 거예요.
왜 거의 모든 codemode 구현이 Javascript를 고르는지 모르겠습니다. 저는 codemode의 언어로 bash를 쓰는 에이전트[1]를 프로토타입으로 만들었는데, 제 생각에는 똑같이 잘 동작했고 따로 가르칠 필요도 없었습니다(LLM에게 codemode를 가르치는 프롬프트는 말 그대로 0입니다. "bash"라는 이름의 도구 하나만 있으면 사용법을 압니다).
[1] https://github.com/ylxdzsw/mu
가장 뻔한 예가
read나view_image입니다. 멀티모달 모델이 이미지를 읽어야 할 때 cat으로는 안 되는데, 하니스가 실제 이미지 페이로드를 LLM의 프로토콜에 주입해야 하기 때문입니다.*님의 프로토타입은 글에서 언급한 한계를 극복했나요?
한계는 없습니다. 에이전트에게 결과를 돌려주는 bash 명령
read나view_image를 만들어 두면 됩니다.그런데 아르민이 그 한계를 언급하지 않았나요?
죄송합니다, 제가 잘 몰라서 그런데, 모델이 이미지를 받을 때 기대하는 프로토콜이 있고 bash는 그걸 지원하지 않는다는 말처럼 들립니다만?
(Bonteq입니다. 다른 계정으로 로그인해 있었네요.)
cat은 못 쓰지만, 에이전트에게 결과를 돌려주는 명령을 만들 수는 있습니다. 아르민도 그 얘기를 하긴 합니다.
그런데 솔직히 왜 조잡한지 모르겠습니다. 저는 tmux에서 pi를 돌리고, 어느 셸 세션/neovim에서든 프롬프트를 보낼 수 있는 CLI를 쓰는데 잘 됩니다. 그러니 이런 통신 방식은 codemode와 별개로 어차피 필요합니다.
JS가 선택된 건 아마 (1) 샌드박스하기 쉽고(QuickJS가 있으니까요) (2) (제 추측입니다만) 일부 모델이 JS Codemode로 사후 학습(post-training)되어 있기 때문일 겁니다.
손과 뇌가 서로 다른 머신에 있으면 훨씬 더 조잡해집니다.
동의하지만, 그건 JS냐 bash냐와는 직교하는 문제입니다. (로컬에서도 bash를 얼마든지 돌릴 수 있으니까요. wasm으로 컴파일한 것이라도요.)
제 관점에서는 그렇지 않습니다. Codemode는 뇌에서 실행되고, bash는 어쩔 수 없이 손이 있는 곳에서 실행되기 때문입니다. 그래서 손이 뇌 안으로 닿아야 한다면, 손에서 뇌로 가는 통신 계층을 따로 깔아야 합니다.
LLM이 base64를 어떻게 디코딩하는지는 분명 알 텐데요, 그게 안 된다면 이미지를 ascii로 바꿔서 보내면 되지 않을까요.
모델들이 code mode용으로 JavaScript를 학습했기 때문입니다. 훨씬 적은 지시만으로 충분합니다. 동시성을 표현하고 싶어 하는데, 그건 전역 Promise와 아주 잘 맞기도 하고요.
하지만 큰 이유는 code mode가 하니스 쪽에서 실행된다는 점입니다. 그래서 bash는 특히 까다로운 대상입니다.
샌드박싱은 분명 장점이지만(중요한 장점일 수 있죠!), 다음과 같습니다.
서로 다른 이야기를 하고 있는 것 같습니다. codemode의 요점은 LLM 쪽 도구 호출을 조율하는 것이지, LLM이 Bash 안에서 실행할 스크립트를 조율하는 것이 아닙니다.
두뇌와 손이 다른 머신에 있는 환경에서는, bash 쪽 손이 하니스인 두뇌에 다시 닿게 하려면 a) 손에 모델이 모르는 도구를 쥐여 줘야 하고 b) 설정이 까다롭습니다. 보통 소켓 기반 역방향 채널 같은 것이 필요하죠.
이걸 꽤 많이 시도해 봤습니다. pi를 항상 손 쪽에 두는 방식이었는데, 복잡성이 크게 늘고 LLM이 이걸 전혀 잘 이해하지 못합니다.
거의 모든 모델이 JS를 충분히 학습했습니다. 따로 가르칠 필요가 없죠.
사실 bash(just-bash나 brush 기반)는 JS나, 훌륭한 임베디드 도구가 있는 lua보다 샌드박싱하기 더 어렵습니다.
안타깝게도 얻는 건 적은데 아주 복잡한 장치를 들이는 것처럼 보입니다. 저는 MCP나, 에이전트가 프로그램으로 엮어 쓸 수 있는 수많은 도구 호출이 필요하지 않습니다. 사실 pi의 약속은 bash 말고는 다른 도구가 사실상 필요 없다는 것이었고요. 최소한으로 건드리는 방법은 도구 호출을 가상의 bash 명령으로 "주입"하는 것이었을 겁니다. code mode도, 손 대 두뇌라는 이분법도 필요 없습니다. LLM이 자기가 고른 언어로 도구와 상호작용하면 되니까요.
파일이 40개 있는데 그중 지워도 되는 쓰레기 파일을 가려내고 싶다고 해 봅시다.
하나씩 순회하면서 파일마다 여러 턴을 쓰면 40*n턴에 컨텍스트도 어마어마해집니다. 아니면 LLM이 "두뇌 안에서" 실행되고 LLM 호출 자체에 접근할 수 있는 스크립트를 쓰게 할 수 있습니다.
그러면 이렇게만 하면 됩니다.
이걸 병렬로 실행할 수도 있고, 호출마다 토큰 비용이 아주 적습니다(전체 컨텍스트가 필요 없고 표적 프롬프트 하나에 훨씬 작은 모델이면 되니까요).