근래에 claude code 와 같은 AI 에이전트를 이것저것 만져보면, 소프트웨어를 쓴다는 느낌이 조금 이상해진다. 기존 소프트웨어는 대체로 길이 있었다. 버튼이 있고, 메뉴가 있고, 폼이 있고, API가 있고, 사용자는 그 안에서 움직인다. 입력을 넣으면 정해진 출력이 나온다. 잘못되면 내가 잘못 눌렀거나, 개발자가 잘못 짰거나, 둘 중 하나였다.
AI 에이전트 쪽은 덜 딱딱하다. 나는 대개 “이 파일 좀 고쳐줘”, “테스트 깨지는 이유 찾아줘” 같은 목표에 가까운 말을 던진다. 그러면 에이전트는 순서를 세우고, tool을 고르고, 중간에 다시 읽고, 다시 판단한다. 물론 안쪽에는 LLM이 있고, 시스템 프롬프트와 권한, tool 목록과 실행 환경이라는 울타리도 있다. 아무렇게나 움직이는 것은 아니다. 그래도 개발자가 모든 분기문을 미리 박아 넣은 프로그램과는 결이 다르다. 프로그램이 실행되는 순간에 작은 로직이 조립되는 느낌이 있다.
Spring을 공부할 때 IoC라는 말을 처음 들으면, 제어가 어디서 어디로 넘어간다는 설명이 나온다. 그때도 사실 말이 좀 거창하다고 생각했다. 그런데 AI 에이전트를 보면서 다시 그 단어가 떠올랐다. 정확히 같은 개념은 아니다. 그래도 제어의 중심이 개발자 손에서 빠져나간다는 느낌은 비슷하다.
예전에는 if-else와 상태 전이를 개발자가 거의 다 써두었다. 입력 A에 결과 B가 안 나오면 버그다. 빠르고, 예측 가능하고, 뒤져볼 곳도 대체로 정해져 있다. 에이전트는 좀 다르다. 사용자의 의도를 해석하고, 지금 쓸 수 있는 tool을 보고, “이 상황에서는 이걸 먼저 해보자"에 가까운 판단을 한다. 그 판단 로직이 소스 코드 안에 단단하게 박혀 있다기보다는 매 요청마다 다시 만들어진다. 예전 소프트웨어가 미리 납땜된 회로라면, AI 에이전트는 실행 중에 배선을 조금씩 바꿔 끼우는 장치 같다. 이 비유가 완벽하다는 뜻은 아니다. 그냥 만지다 보면 그런 감각이 온다.
여기서 개발자 이야기를 안 할 수가 없다. 에이전트 성능이 계속 좋아지면, 개발자가 코딩과 디버깅에서 완전히 해방된다는 말은 아직 좀 성급하게 느껴진다. 실제로는 더 많이 읽고, 더 많이 의심하고, 이상한 결과를 더 빨리 잡아내야 할지도 모른다. 그래도 코드를 직접 짜는 능력만으로 변별력을 만들기는 점점 어려워질 것 같다. 씁쓸하지만 그렇다.
문법을 알고, 프레임워크 베스트 프랙티스를 외우고, 적당히 잘 쪼개는 능력은 여전히 필요하다. 다만 그것만으로는 부족해질 가능성이 크다. 무엇을 만들어야 하는지, 왜 만들어야 하는지, 어디까지가 진짜 문제이고 어디서부터는 그냥 기분 문제인지 가르는 쪽이 더 앞에 온다. 비즈니스 요구사항이라는 흐릿한 말을 실제로 돌아가는 스펙으로 바꾸는 일. 사람들마다 다르게 말하는 “그거 좀 되게 해주세요"를 덜 모호한 문장으로 만드는 일. 개발이라는 일이 원래도 이쪽에 가까웠는데, 구현 난이도가 그 사실을 가리고 있었던 것 아닌가 싶다. 코드가 어렵다 보니 코드가 전부처럼 보였던 거다.
액체 같은 소프트웨어
여기서부터는 망상에 가깝다. 그래도 요즘 속도를 보고 있으면, 완전히 헛소리라고 치우기도 어렵다.
지금까지의 소프트웨어는 대체로 고체였다. 한 번 빌드해서 배포하면, 누가 코드를 고치고 다시 배포하기 전까지는 그 모양을 유지한다. 설정값이나 feature flag로 조금씩 휘게 만들 수는 있지만, 그 유연함도 개발자가 미리 만든 홈 안에서만 움직인다. 홈이 없으면 못 간다.
앞으로는 조금 더 액체 같은 소프트웨어가 나올 수도 있겠다는 생각을 한다. 필요한 로직을 JIT (Just-In-Time)으로 그때그때 만들고, 요청이 들어온 순간 그 요청에 맞춘 일회성 실행 조각을 만들었다가, 일을 끝내면 버리는 식이다. 비슷한 요청이 계속 들어오고 유효하다고 판단되면 캐싱하거나 정식 경로로 올릴 수도 있겠다. 지금은 함수나 클래스를 미리 만들어두고 부른다. 나중에는 어떤 실행 조각은 잠깐 생겼다가 사라지고, 어떤 조각은 살아남고, 또 어떤 조각은 다음 요청에서 조금 다른 모양으로 바뀔지도 모른다.
조금 더 밀어붙이면 이런 그림도 가능하다. 시스템이 자기 부하를 보다가 특정 구간에 병목이 생긴다. 지금이라면 autoscaling을 걸거나 캐시를 붙이거나, 개발자가 들어가서 구조를 바꾼다. 그런데 에이전트가 요청 패턴과 레이어별 비용을 보고 임시 구조를 직접 조립한다면? 어느 캐시 전략이 나은지, 어떤 경로를 우회하는 편이 싼지 판단해서 런타임에서 움직인다면? 상상만 해도 운영자는 피곤해진다. 개발자도 그렇고. 그래도 효용이 크면 누군가는 반드시 밀어붙일 것이다.
찜찜한 부분은 너무 많다. 우선 재현성이 약해진다. 같은 요청을 어제는 이렇게 처리하고 오늘은 저렇게 처리하는 일이 자연스러워질 수 있다. 소프트웨어에서 “다시 해보면 똑같이 된다"는 감각은 꽤 큰 버팀목이었는데, 그게 흔들린다. 장애 분석, 감사 로그, 테스트 전략, 책임 추적이 한꺼번에 어려워진다. 그냥 어려운 정도가 아니라, 우리가 익숙한 방식으로는 설명이 잘 안 되는 구간이 생긴다.
책임 문제도 남는다. 동적으로 생성된 로직이 개인 정보를 이상한 경로로 흘려보냈다면, 특정 사용자군에게만 불리한 결정을 했다면, 설명하기 어려운 우회 동작을 만들었다면 누가 책임을 질까. “모델이 그렇게 판단했습니다"라는 말은 생각보다 아무것도 해결해주지 않는다. 법적 책임도, 운영 책임도, 밤에 알람 받고 일어나는 사람의 피곤함도 대신 져주지 않는다.
그런데도 이게 마냥 비현실적으로 느껴지지 않는 건, 산업이 종종 비용 대비 효용 앞에서 원칙을 다시 쓰기 때문이다. LLM은 계속 가벼워지고 있고, 온디바이스 AI나 추론 최적화가 더 붙으면 단위 요청당 비용도 지금보다 내려갈 것이다. 그 순간 재현성, 고정된 로직, 개발자의 명시적 제어 같은 말들은 절대 조건이 아니라 선택지처럼 취급될 수 있다. 나는 그게 반갑다고만 말하기는 어렵다. 그래도 요즘 claude code를 켜놓고 일을 시켜보면, 소프트웨어가 조금씩 물러지고 있다는 느낌을 지우기가 어렵다.