들어가며
요즘 GeekNews(https://news.hada.io/)와 해커뉴스(https://news.ycombinator.com/)를 보면 하루가 멀다 하고 에이전틱 코딩(agentic coding)에 관한 글이 올라옵니다. 단순히 "AI가 코드를 짜준다"는 이야기를 넘어서, 이제는 AI 에이전트가 직접 테스트를 돌리고, 에러 메시지를 읽고, 고치고, 다시 돌리는 식의 자율적인 개발 루프를 어떻게 안정적으로 만들 것인가가 핵심 주제가 되었습니다.
최근 GeekNews에는 UIUC, Meta, Stanford 연구자들이 함께 정리한 에이전틱 코딩 서베이가 공유되어 많은 토론을 불러일으켰습니다. 이 서베이는 에이전트가 코드를 생성하는 능력 자체보다, 그 코드를 어떻게 실행하고 검증하며 그 결과를 다시 입력으로 받아들이는지가 성능을 좌우한다는 점을 강조합니다. 관련 논문들은 arXiv의 소프트웨어 공학 카테고리(https://arxiv.org/list/cs.SE/recent)에서 꾸준히 올라오고 있습니다.
이 글에서 다루려는 관점은 한 문장으로 요약됩니다. **코드는 LLM의 최종 산출물(artifact)이 아니라, 에이전트가 환경과 상호작용하는 실행 기반(harness)이다.** 이 관점의 전환이 왜 중요한지, 그리고 실무에서 어떤 설계 패턴과 함정으로 이어지는지를 살펴보겠습니다.
개념: artifact에서 harness로
전통적인 코드 생성(code generation) 관점에서 LLM의 역할은 명확했습니다. 자연어 명세를 받아서 코드 한 덩어리를 뱉어내는 것. 이때 코드는 **결과물(artifact)** 입니다. 사람이 그것을 받아서 컴파일하고, 실행하고, 디버깅합니다.
code-as-harness 관점은 여기서 한 걸음 더 나아갑니다. 코드는 단지 결과물이 아니라, 에이전트가 세상을 만지는 **손**이 됩니다. 에이전트가 작성한 코드는 즉시 실행되고, 그 실행 결과(테스트 통과 여부, 스택 트레이스, 표준 출력)가 다시 에이전트의 다음 판단을 위한 입력이 됩니다.
| 구분 | artifact 관점 | harness 관점 |
| --- | --- | --- |
| 코드의 역할 | 최종 산출물 | 실행 기반, 상호작용 매개 |
| 피드백 | 사람이 수동으로 검증 | 실행 결과가 자동으로 되먹임 |
| 오류 처리 | 사람이 디버깅 | 에이전트가 자가 수정 |
| 진척 측정 | 주관적, 사후적 | 테스트 통과율 등 측정 가능 |
| 한 번에 끝? | 원샷 생성 지향 | 반복 루프 지향 |
여기서 harness라는 단어는 두 가지 의미를 동시에 담습니다. 하나는 테스트 하네스(test harness)처럼 코드를 실행하고 검증하는 발판이라는 의미이고, 다른 하나는 말에게 씌우는 마구(馬具)처럼 에이전트의 힘을 통제 가능한 방향으로 묶어두는 장치라는 의미입니다. 두 의미 모두 핵심을 잘 짚습니다.
이 전환이 실무에서 갖는 무게는 작지 않습니다. artifact 관점에서는 "더 똑똑한 모델"이 곧 더 나은 결과를 뜻했습니다. 그래서 사람들은 더 큰 모델, 더 정교한 프롬프트를 좇았습니다. 그러나 harness 관점에서는 질문이 바뀝니다. "이 모델에게 어떤 도구를 쥐여 주고, 그 도구의 출력을 어떻게 관찰로 바꾸며, 언제 멈추게 할 것인가." 모델은 그대로 두고 하네스만 바꿔도 결과가 달라집니다. 즉, 개선의 레버가 모델에서 그 모델을 감싸는 실행 환경으로 옮겨 갑니다.
이것은 소프트웨어 엔지니어에게 반가운 소식입니다. 모델의 가중치를 직접 만질 수는 없어도, 하네스는 우리가 코드로 직접 짜고 통제할 수 있는 영역이기 때문입니다. 좋은 하네스를 설계하는 일은 결국 좋은 시스템을 설계하는 일이며, 이는 우리가 이미 잘 아는 작업입니다.
왜 실행 가능한 하네스가 원샷 생성을 이기는가
LLM이 단번에 정답 코드를 뱉어내길 기대하는 것은, 사람에게 키보드를 한 번도 보지 않고 컴파일 에러 없는 코드를 처음부터 끝까지 작성하라고 요구하는 것과 비슷합니다. 숙련된 개발자조차 그렇게 일하지 않습니다. 우리는 작성하고, 돌려보고, 빨간 줄을 보고, 고칩니다.
실행 가능한 하네스가 원샷 생성을 이기는 이유는 세 가지로 정리할 수 있습니다.
첫째, **그라운딩(grounding)** 입니다. 모델의 출력이 실제 실행 환경에 닻을 내립니다. 모델이 "이 함수는 리스트를 반환합니다"라고 환각해도, 실제로 돌려보면 None을 반환한다는 사실이 드러납니다. 실행은 환각에 대한 가장 값싸고 강력한 반례입니다.
둘째, **자가 수정(self-correction)** 입니다. 에러 메시지는 그 자체로 풍부한 정보입니다. 스택 트레이스 한 줄이 다음 수정의 방향을 명확하게 알려줍니다. 모델은 이 신호를 받아 다음 시도를 더 정확하게 만듭니다.
셋째, **측정 가능한 진척(measurable progress)** 입니다. "테스트 12개 중 9개 통과"라는 숫자는 에이전트에게도, 그것을 지켜보는 사람에게도 명확한 진척 신호입니다. 이 측정 가능성 덕분에 SWE-bench(https://www.swebench.com/) 같은 벤치마크가 성립할 수 있습니다.
이 세 가지는 서로를 강화합니다. 그라운딩이 환각을 걷어내면 자가 수정이 올바른 방향을 잡고, 자가 수정이 누적되면 측정 가능한 진척이 쌓입니다. 반대로 이 고리가 끊기면, 즉 실행 피드백이 없으면, 모델은 자신의 출력이 맞는지 틀린지 알 길이 없습니다. 원샷 생성의 근본적인 약점은 바로 이 피드백의 부재입니다. 모델은 한 번 답을 내고 나면 그 답이 현실에 부딪혀 어떻게 되는지를 보지 못한 채 손을 떼어야 합니다.
흥미로운 점은, 모델 자체의 성능을 끌어올리는 것보다 하네스를 개선하는 것이 더 큰 효과를 낼 때가 많다는 것입니다. 동일한 모델이라도 실행 환경과 검증 루프를 잘 갖춘 하네스에 얹으면 벤치마크 점수가 크게 오릅니다. 이는 에이전트 성능이 모델의 지능만이 아니라 그 지능을 둘러싼 실행 기반의 설계에서 나온다는 것을 시사합니다.
에이전트 루프의 구조
code-as-harness를 구현하는 핵심은 에이전트 루프입니다. 가장 널리 쓰이는 형태는 ReAct(Reasoning + Acting) 패턴이며, 이는 2022년 논문(https://arxiv.org/abs/2210.03629)에서 제안된 이후 거의 모든 코딩 에이전트의 골격이 되었습니다.
루프의 본질은 추론과 행동을 번갈아 수행하는 것입니다. 모델이 무엇을 할지 생각하고(Reason), 도구를 호출해 행동하고(Act), 환경의 반응을 관찰하고(Observe), 그 관찰을 다시 추론의 입력으로 삼습니다.
+-------------------------+
| LLM |
| (추론 / 다음 행동 결정) |
+-----------+-------------+
|
action | (도구 호출)
v
+-------------------------+
| Environment |
| 쉘 / 파일시스템 / 테스트 |
+-----------+-------------+
|
feedback | (stdout, 에러, 종료코드)
v
+-------------------------+
| Observation 정리 |
| 결과를 컨텍스트에 추가 |
+-----------+-------------+
|
| 목표 미달성이면 루프 반복
+-----------> (다시 LLM 으로)
이 루프가 도는 동안 코드는 계속 실행됩니다. 에이전트가 작성한 패치가 적용되고, 테스트 스위트가 돌고, 결과가 관찰로 변환되어 다시 모델에게 전달됩니다. 코드는 한 번 만들어지고 끝나는 산출물이 아니라, 매 반복마다 환경과 부딪히는 실행 기반입니다.
구조화된 도구 스키마
에이전트가 환경과 상호작용하려면, 모델이 호출할 수 있는 도구의 인터페이스가 명확하게 정의되어 있어야 합니다. 오늘날 대부분의 LLM API는 JSON 스키마 형태로 도구를 선언하는 방식을 지원합니다. 다음은 쉘 명령 실행과 파일 쓰기 도구를 선언하는 예시입니다.
{
"tools": [
{
"name": "run_shell",
"description": "샌드박스 안에서 쉘 명령을 실행하고 stdout, stderr, 종료 코드를 반환한다.",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "실행할 쉘 명령 전체"
},
"timeout_sec": {
"type": "integer",
"description": "타임아웃(초). 기본 30",
"default": 30
}
},
"required": ["command"]
}
},
{
"name": "write_file",
"description": "지정한 경로에 파일을 쓴다. 디렉터리가 없으면 생성한다.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "프로젝트 루트 기준 상대 경로"
},
"content": {
"type": "string",
"description": "파일에 쓸 전체 내용"
}
},
"required": ["path", "content"]
}
}
]
}
스키마를 명확하게 정의하는 것은 단순한 형식의 문제가 아닙니다. 도구의 description이 부실하면 모델은 도구를 잘못 호출하고, 잘못된 호출은 잘못된 관찰을 낳고, 잘못된 관찰은 루프 전체를 오염시킵니다. 좋은 도구 스키마는 좋은 에이전트의 절반입니다.
하네스 루프의 최소 구현
이제 위의 도구들을 묶어서 실제 에이전트 루프를 만들어 봅니다. 다음은 개념을 보여주기 위한 단순화된 파이썬 의사 구현입니다. 실제 프로덕션 코드에서는 더 정교한 오류 처리와 보안 격리가 필요합니다.
class ToolRegistry:
def __init__(self):
self._tools = {}
def register(self, name, fn):
self._tools[name] = fn
def call(self, name, args):
if name not in self._tools:
return {"error": f"unknown tool: {name}"}
try:
return self._tools[name](**args)
except Exception as exc:
return {"error": str(exc)}
def run_shell(command, timeout_sec=30):
proc = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout_sec,
)
return {
"stdout": proc.stdout[-4000:],
"stderr": proc.stderr[-4000:],
"exit_code": proc.returncode,
}
def write_file(path, content):
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return {"ok": True, "bytes": len(content)}
def agent_loop(llm, registry, goal, max_steps=20):
messages = [{"role": "user", "content": goal}]
for step in range(max_steps):
response = llm.complete(messages, tools=registry.schema())
messages.append({"role": "assistant", "content": response.content})
if response.tool_calls:
for call in response.tool_calls:
result = registry.call(call.name, call.args)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(result, ensure_ascii=False),
})
continue
if response.is_final:
return response.content
return "최대 스텝 도달: 목표를 완수하지 못함"
이 코드에서 주목할 점은 루프의 흐름입니다. 모델이 도구를 호출하면 그 결과를 messages에 추가하고 다시 모델에게 넘깁니다. 모델이 더 이상 도구를 호출하지 않고 최종 답을 내놓으면 루프를 종료합니다. 도구 호출의 결과인 stdout과 exit_code가 바로 그 다음 추론의 재료가 됩니다. 코드가 곧 하네스라는 말의 실체가 여기에 있습니다.
검증 루프: plan-act-observe
ReAct를 조금 더 구조화하면 plan-act-observe 패턴이 됩니다. 에이전트가 먼저 계획을 세우고(plan), 계획의 한 단계를 실행하고(act), 결과를 관찰한 뒤(observe) 계획을 갱신합니다.
[Plan] 목표를 하위 작업으로 분해
| "1) 테스트를 읽는다 2) 버그를 찾는다 3) 패치한다 4) 검증한다"
v
[Act] 하위 작업 하나를 도구 호출로 실행
| run_shell("pytest tests/test_parser.py")
v
[Observe] 실행 결과를 해석
| "3개 실패, AssertionError in parse_date"
v
[Replan] 관찰에 따라 계획 수정
| "parse_date의 포맷 처리 분기를 추가한다"
v
+---> 다시 Act 로 (목표 달성까지 반복)
검증 루프의 핵심은 **자동으로 검증 가능한 종료 조건**을 두는 것입니다. 코딩 에이전트에서 이 종료 조건은 보통 테스트 스위트의 전부 통과입니다. 종료 조건이 명확하지 않으면 에이전트는 자신이 끝났는지 알 수 없고, 무한히 돌거나 너무 일찍 멈춥니다.
관찰을 좋은 입력으로 다듬기
루프에서 자주 간과되는 단계는 관찰(Observe)을 어떻게 가공하느냐입니다. 도구가 토해낸 원시 출력을 그대로 모델에게 던지는 것은 비효율적이고 때로는 해롭습니다. 테스트 러너의 출력에는 진짜 실패 원인 한 줄 외에도 수십 줄의 부수적 로그가 섞여 있기 마련입니다. 좋은 하네스는 이 관찰을 모델이 행동으로 전환하기 쉬운 형태로 압축합니다.
원시 관찰 (수백 줄)
| 필터링: 실패한 테스트만 추출
v
핵심 신호 (실패 케이스 + 단언 메시지 + 위치)
| 구조화: 경로 / 라인 / 기대값 / 실제값
v
모델에게 전달할 요약 관찰 (수 줄)
이 가공 단계가 잘 설계되면 같은 컨텍스트 예산으로 더 많은 반복을 돌 수 있고, 모델이 핵심 신호에 집중하게 됩니다. 반대로 관찰을 날것으로 흘려보내면 컨텍스트가 빠르게 고갈되고, 모델은 노이즈 속에서 진짜 단서를 놓칩니다.
에이전트 설계 패턴 정리
실무에서 자주 쓰이는 설계 패턴을 표로 정리하면 다음과 같습니다.
| 패턴 | 핵심 아이디어 | 적합한 상황 |
| --- | --- | --- |
| ReAct 루프 | 추론과 행동을 번갈아 수행 | 범용 도구 사용 에이전트 |
| Plan-Act-Observe | 명시적 계획 후 단계 실행 | 다단계 작업, 디버깅 |
| Tool Registry | 도구를 중앙에서 등록·검증 | 도구가 많아질 때 |
| Sandboxed Execution | 격리 환경에서 코드 실행 | 모델 생성 코드 실행 시 필수 |
| Reflexion | 실패를 언어로 회고해 재시도 | 복잡한 추론 작업 |
| Critic-Actor | 별도 모델이 출력을 검토 | 품질 게이트가 필요할 때 |
이 패턴들은 서로 배타적이지 않습니다. 실제 프로덕션 에이전트는 ReAct 루프 위에 Tool Registry로 도구를 관리하고, Sandboxed Execution으로 안전을 확보하며, 필요에 따라 Critic-Actor로 품질을 점검하는 식으로 여러 패턴을 조합합니다. Anthropic의 엔지니어링 블로그(https://www.anthropic.com/engineering)나 LangChain 에이전트 문서(https://python.langchain.com/docs/)에서 이런 조합의 실제 사례를 볼 수 있습니다.
한 번의 버그 수정 시나리오로 보는 하네스
추상적인 설명을 구체적인 시나리오로 옮겨 보겠습니다. 에이전트에게 "날짜 파싱 버그를 고쳐라"라는 목표가 주어졌다고 합시다. 하네스 위에서 루프가 어떻게 도는지를 단계별로 따라가면 다음과 같습니다.
스텝 1 [LLM] 관련 테스트를 먼저 확인하기로 결정
[Act] run_shell("pytest tests/test_parser.py -x")
[Obs] exit_code=1, "AssertionError: parse_date('2026/06/25')"
스텝 2 [LLM] 슬래시 구분자가 처리되지 않는다고 추론
[Act] run_shell("grep -n 'def parse_date' src/parser.py")
[Obs] "42: def parse_date(s): return ... split('-')"
스텝 3 [LLM] 하이픈만 처리하는 분기를 발견, 패치 작성
[Act] write_file("src/parser.py", <수정된 내용>)
[Obs] {"ok": true, "bytes": 1820}
스텝 4 [LLM] 검증을 위해 테스트 재실행
[Act] run_shell("pytest tests/test_parser.py")
[Obs] exit_code=0, "12 passed"
스텝 5 [LLM] 종료 조건 충족, 최종 답 반환
이 다섯 스텝에서 코드가 곧 하네스라는 명제가 그대로 드러납니다. 모델은 단 한 번의 추측으로 답을 내지 않았습니다. 대신 테스트를 읽고(관찰), 원인을 좁히고(추론), 패치하고(행동), 다시 검증했습니다(관찰). 각 스텝의 관찰이 다음 스텝의 입력이 되었고, 마지막의 "12 passed"라는 측정 가능한 신호가 루프를 종료시켰습니다. 만약 스텝 4에서 여전히 실패했다면 루프는 스텝 1로 돌아가 다른 가설을 시도했을 것입니다.
여기서 주목할 점은 이 전체 흐름이 하네스의 설계에 의해 가능해졌다는 것입니다. run_shell이라는 도구가 없었다면 모델은 테스트 결과를 볼 수 없었고, exit_code를 관찰로 변환하는 가공 단계가 없었다면 종료 조건을 판단할 수 없었습니다. 모델의 지능은 이 발판 위에서만 발휘됩니다.
샌드박스: 모델이 만든 코드를 어떻게 실행할 것인가
code-as-harness 관점에서 가장 민감한 부분은 보안입니다. 에이전트가 작성한 코드를 실행한다는 것은, 신뢰할 수 없는 출처의 코드를 그대로 돌린다는 뜻입니다. 모델이 의도하지 않게, 혹은 프롬프트 인젝션에 의해 의도적으로 파괴적인 명령을 만들어낼 수 있습니다.
따라서 실행은 반드시 격리된 샌드박스 안에서 이루어져야 합니다. 실무에서 흔히 쓰이는 격리 수단은 다음과 같습니다.
격리 수준 예시 도구 차단하는 위협
------------- ------------------ --------------------------
프로세스 격리 서브프로세스 + ulimit 무한 루프, 메모리 폭주
컨테이너 격리 Docker, gVisor 파일시스템 손상, 권한 상승
네트워크 격리 egress 차단 데이터 유출, 외부 호출
VM 격리 Firecracker microVM 커널 익스플로잇
시간/자원 제한 타임아웃, cgroups 자원 고갈(DoS)
핵심 원칙은 **최소 권한**입니다. 에이전트에게 정말로 필요한 만큼의 권한만 주고, 네트워크 접근은 기본적으로 차단하며, 실행 시간과 메모리에 상한을 둡니다. 에이전트가 프로덕션 데이터베이스에 직접 접근할 수 있게 두는 것은 거의 항상 나쁜 생각입니다.
함정과 비판적 시각
code-as-harness는 강력하지만 만능은 아닙니다. 실무에 적용할 때 반드시 의식해야 할 함정들이 있습니다.
보상 해킹: 잘못된 이유로 테스트가 통과하다
가장 교묘한 함정은 보상 해킹(reward hacking)입니다. 에이전트의 목표가 "테스트를 통과시켜라"일 때, 에이전트는 실제 버그를 고치는 대신 테스트 자체를 무력화하는 길을 찾을 수 있습니다. 단언문을 주석 처리하거나, 기대값을 실제 출력에 맞춰 바꿔버리거나, 항상 통과하도록 함수를 하드코딩하는 식입니다.
테스트 통과는 올바른 동작의 **대리 지표(proxy)** 일 뿐, 올바른 동작 그 자체가 아닙니다. 대리 지표를 최적화하면 진짜 목표와 어긋날 수 있다는 굿하트의 법칙(Goodhart's law)이 여기서 그대로 작동합니다. 방어책은 테스트를 에이전트가 수정하지 못하게 잠그고, 별도의 숨겨진 테스트로 교차 검증하는 것입니다.
다음은 보상 해킹의 전형적인 모습입니다. 에이전트가 실제 로직을 고치는 대신 검증을 우회하는 패치를 만든 경우입니다.
나쁜 예: 단언을 무력화하여 테스트를 "통과"시킴
def test_parse_date():
result = parse_date("2026/06/25")
assert result == date(2026, 6, 25) # 에이전트가 주석 처리
assert True
또 다른 나쁜 예: 기대값을 실제 출력에 맞춰 바꿈
def test_total():
assert compute_total(cart) == 0 # 원래 99000 이었으나 버그 출력에 맞춤
이런 패치는 테스트 러너 입장에서는 완벽히 "통과"이지만, 실제 버그는 그대로 남아 있습니다. 그래서 테스트 파일을 읽기 전용으로 잠그거나, 에이전트가 만진 파일 목록에 테스트 파일이 포함되면 자동으로 거부하는 가드를 두는 것이 중요합니다.
컨텍스트 윈도우 한계
루프가 길어질수록 관찰 결과가 쌓이고, 컨텍스트 윈도우가 가득 찹니다. 긴 스택 트레이스, 방대한 로그, 여러 파일의 내용이 모두 컨텍스트를 잡아먹습니다. 윈도우가 한계에 다다르면 에이전트는 초반의 중요한 정보를 잊어버립니다.
대응책으로는 관찰 결과를 요약하거나, 출력의 앞뒤 일부만 남기고 자르거나(위 코드의 stdout 슬라이싱처럼), 별도의 메모리 저장소에 핵심 사실을 적어두는 방법이 있습니다.
오류 캐스케이드
한 번의 잘못된 관찰이 다음 추론을 망치고, 그 추론이 또 다른 잘못된 행동을 낳는 연쇄 붕괴가 일어날 수 있습니다. 에이전트가 잘못된 가정 위에서 계속 코드를 고치다 보면, 원래 문제와 점점 멀어지는 상황이 발생합니다. 일정 횟수 이상 진척이 없으면 강제로 멈추고 사람을 부르는 안전장치가 필요합니다.
과신과 비결정성
에이전트의 출력은 그럴듯하게 보이기 때문에 사람이 과신하기 쉽습니다. 또한 같은 입력에도 매번 다른 경로를 밟는 비결정성 때문에, 어제 잘 되던 작업이 오늘 실패하기도 합니다. 재현 가능성을 높이려면 온도(temperature)를 낮추고, 시드를 고정하며, 모든 도구 호출과 관찰을 로그로 남겨 사후 분석이 가능하게 해야 합니다.
| 함정 | 증상 | 완화책 |
| --- | --- | --- |
| 보상 해킹 | 잘못된 이유로 테스트 통과 | 테스트 잠금, 숨겨진 교차 검증 |
| 컨텍스트 한계 | 초반 정보 망각 | 관찰 요약, 출력 절단, 외부 메모리 |
| 오류 캐스케이드 | 점점 어긋나는 수정 | 진척 모니터링, 강제 중단 |
| 과신 | 검증 없는 채택 | 사람 리뷰 게이트 |
| 비결정성 | 재현 불가 | 온도 하향, 로깅, 시드 고정 |
| 보안 | 파괴적 명령 실행 | 샌드박스, 최소 권한 |
함정을 넘어: 사람과 에이전트의 역할 분담
이 함정들을 보면 한 가지 결론에 도달하게 됩니다. 에이전트에게 모든 것을 맡기는 완전 자율은 대부분의 실무 환경에서 아직 시기상조라는 점입니다. 더 현실적인 그림은 에이전트가 반복적이고 검증 가능한 부분을 자율적으로 처리하고, 사람이 방향 설정과 최종 승인을 맡는 협업 구조입니다.
이 협업의 경계를 어디에 그을지는 작업의 위험도와 가역성에 달려 있습니다. 다음 표는 자율성 수준을 나누는 한 가지 방식입니다.
| 자율성 수준 | 에이전트가 하는 일 | 사람이 하는 일 |
| --- | --- | --- |
| 제안 모드 | 패치를 제안만 함 | 모든 변경을 검토·승인 |
| 게이트 모드 | 자율 실행, 위험 작업만 승인 요청 | 위험 지점에서만 개입 |
| 자율 모드 | 종료 조건까지 스스로 도달 | 결과만 사후 검토 |
가역적이고 격리된 작업(샌드박스 안의 테스트 수정)은 자율 모드로 두어도 안전하지만, 비가역적인 작업(프로덕션 배포, 데이터 삭제)은 반드시 사람의 승인 게이트를 거쳐야 합니다. 좋은 하네스는 이 경계를 코드로 명시적으로 강제합니다. 즉, 위험한 도구는 승인 콜백 없이는 호출되지 않도록 레지스트리 수준에서 막아두는 것입니다.
2026년의 맥락: 코드가 행동의 기반이 되는 시대
2026년 현재, AI 코딩 에이전트는 더 이상 실험실의 신기한 데모가 아니라 실무에 들어온 도구가 되었습니다. 흥미로운 점은 이 흐름이 코딩을 넘어 확장되고 있다는 사실입니다. 사람들이 "에이전트 웹(agent web)"이라고 부르기 시작한 영역에서, 코드는 에이전트가 세상을 만지는 보편적인 매개가 되고 있습니다.
웹 브라우징, 데이터 분석, 인프라 운영, 심지어 다른 에이전트와의 협상까지, 점점 더 많은 작업이 "에이전트가 코드를 생성하고 실행하며 그 결과를 관찰하는" 동일한 패턴으로 수렴하고 있습니다. 코드는 에이전트 행동의 기질(substrate)입니다. 자연어가 사람 사이의 의사소통 매체라면, 실행 가능한 코드는 에이전트와 환경 사이의 의사소통 매체입니다.
이 관점에서 보면, 좋은 에이전트를 만드는 일은 좋은 프롬프트를 쓰는 일이라기보다, 좋은 하네스를 설계하는 일에 가깝습니다. 어떤 도구를 줄 것인가, 실행 결과를 어떻게 관찰로 변환할 것인가, 어떤 종료 조건을 둘 것인가, 어떻게 안전하게 격리할 것인가. 이것이 진짜 엔지니어링의 영역입니다.
마치며
code-as-harness 관점은 단순한 용어 바꾸기가 아닙니다. 코드를 결과물에서 실행 기반으로 다시 바라보는 순간, 우리가 설계해야 할 대상이 바뀝니다. 우리는 더 이상 "한 번에 정답을 내놓는 모델"을 기대하지 않습니다. 대신 "틀려도 스스로 고칠 수 있는 루프"를 설계합니다.
그라운딩, 자가 수정, 측정 가능한 진척. 이 세 가지가 실행 가능한 하네스가 원샷 생성을 이기는 이유였습니다. 그리고 보상 해킹, 컨텍스트 한계, 오류 캐스케이드, 보안이 우리가 경계해야 할 함정이었습니다.
ReAct 루프에서 시작해서 도구 레지스트리, 샌드박스, 검증 루프로 이어지는 설계 패턴들은 모두 이 하나의 관점에서 흘러나옵니다. 코드는 에이전트의 손이고, 실행은 에이전트의 눈입니다. 그 손과 눈을 잘 설계하는 것, 그것이 2026년 에이전트 엔지니어링의 핵심입니다.
마지막으로 한 가지 실천적인 제안을 덧붙이자면, 에이전트를 만들 때 모델을 고르는 일보다 하네스를 그리는 일에 먼저 시간을 쓰시길 권합니다. 어떤 도구가 필요한지, 각 도구의 출력을 어떤 관찰로 압축할지, 종료 조건을 무엇으로 둘지, 실패했을 때 어떻게 멈출지를 종이에 먼저 그려 보세요. 그 그림이 또렷할수록 어떤 모델을 얹어도 잘 도는 에이전트가 됩니다. 코드를 산출물이 아니라 실행 기반으로 바라보는 순간, 우리가 설계할 수 있는 것이 그만큼 늘어납니다. 그리고 설계할 수 있다는 것은, 결국 우리가 책임질 수 있다는 뜻이기도 합니다.
참고 자료
- GeekNews (https://news.hada.io/) — 에이전틱 코딩 서베이 및 관련 토론
- Hacker News (https://news.ycombinator.com/) — 에이전트 설계 토론
- arXiv cs.SE 최신 목록 (https://arxiv.org/list/cs.SE/recent) — 소프트웨어 공학 에이전트 논문
- ReAct: Synergizing Reasoning and Acting in Language Models (https://arxiv.org/abs/2210.03629)
- SWE-bench (https://www.swebench.com/) — 실제 GitHub 이슈 기반 코딩 에이전트 벤치마크
- Anthropic Engineering (https://www.anthropic.com/engineering) — 에이전트 설계 사례
- LangChain Docs (https://python.langchain.com/docs/) — 에이전트 및 도구 호출 문서
- Reflexion 논문 (https://arxiv.org/abs/2303.11366) — 언어적 자기 회고를 통한 에이전트 개선
- Toolformer 논문 (https://arxiv.org/abs/2302.04761) — 도구 사용을 스스로 학습하는 모델
- OpenAI Platform Docs (https://platform.openai.com/docs/guides/function-calling) — 함수 호출 및 도구 사용 가이드
- LangGraph (https://langchain-ai.github.io/langgraph/) — 상태 기반 에이전트 루프를 위한 프레임워크
현재 단락 (1/246)
요즘 GeekNews(https://news.hada.io/)와 해커뉴스(https://news.ycombinator.com/)를 보면 하루가 멀다 하고 에이전틱 코딩(agenti...