에이전트는 지시보다 반응으로 움직일 때 강해진다
TypeScript 기반 reactive AI agent framework를 다루는 글에 맞춰, 중앙 오케스트레이션 대신 공유된 agentic environment와 event-driven 반응성이 왜 중요한지 풀어낸다. 동시성, context 흐름, tool 호출, 설계 함정을 함께 짚는 방향의 에세이에 맞춘 메타데이터다.
AI agent를 여러 개 붙여 놓는 순간 가장 먼저 무너지는 것은 추론 능력이 아니라 순서에 대한 환상이다.
처음에는 대개 이렇게 설계한다. 하나의 coordinator가 있고, 모든 agent는 그 coordinator의 지시를 받아 움직인다. 어떤 agent는 검색을 하고, 어떤 agent는 요약을 하고, 어떤 agent는 tool을 호출한다. 흐름도는 깔끔하다. 누가 먼저 시작하고, 누가 끝나면 누가 이어받는지 한눈에 보인다. 문서로 설명하기도 쉽다. 문제는 그 구조가 현실의 작업 속도를 닮지 않았다는 데 있다. 현실의 작업은 한 줄로 흐르지 않는다. 입력은 중간에 바뀌고, 우선순위는 갑자기 뒤집히며, 새로 들어온 사실 하나가 이미 끝난 판단을 다시 흔든다. 이때 중앙에서 모든 것을 지시하는 구조는 통제력을 주는 대신 반응성을 빼앗는다.
TypeScript 기반의 reactive AI agent framework가 흥미로운 이유는 바로 이 지점에 있다. 핵심은 agent를 더 똑똑하게 만드는 데만 있지 않다. 오히려 agent가 자기 차례를 기다리지 않고, 공유된 environment 안에서 상태 변화와 이벤트에 반응하도록 만드는 데 있다. 이것은 단순한 구현 취향의 차이가 아니다. agent 시스템을 workflow로 볼 것인가, 살아 있는 runtime으로 볼 것인가의 차이다.
흐름을 설계할 것인가, 조건을 설계할 것인가
전통적인 orchestration은 흐름을 설계한다. A가 끝나면 B, B가 실패하면 C, C가 특정 값을 내면 D. 이 방식은 안정적이지만, 실제로는 모든 중요한 판단을 coordinator 하나가 떠안게 만든다. 무슨 일이 벌어졌는지 해석하는 책임, 누구를 깨울지 결정하는 책임, context를 압축해서 전달하는 책임이 한곳에 몰린다. 처음엔 명확해 보여도 시간이 갈수록 coordinator는 거대한 스위치보드가 된다.
reactive 구조는 다른 질문을 던진다. “누가 누구에게 지시할까”가 아니라 “어떤 변화가 누구를 깨울까”를 묻는다. 파일이 추가되면 검색 agent가 반응하고, 검색 결과의 신뢰도가 일정 기준 아래면 검증 agent가 반응하고, 검증 결과가 충돌하면 planning 성격의 agent가 다시 개입한다. 중요한 것은 각 agent가 전체 흐름을 다 알 필요가 없다는 점이다. 자신이 구독하는 사건과 자신이 갱신하는 상태만 제대로 이해하면 된다.
이 차이는 시스템이 커질수록 극적으로 드러난다. 중앙 orchestration에서는 새로운 예외가 생길 때마다 coordinator에 분기가 추가된다. reactive 구조에서는 새로운 예외를 처리하는 agent를 하나 더 붙이거나, 기존 이벤트에 새로운 반응 규칙을 얹으면 된다. 복잡성이 사라지는 것은 아니다. 다만 복잡성이 하나의 뇌에 쌓이지 않고 환경 전체로 분산된다.
공유된 environment가 왜 중요한가
agent가 이벤트에 반응한다는 말은 듣기 좋지만, 실제 구현에서는 곧바로 난관에 부딪힌다. 서로 다른 agent가 같은 사실을 어떻게 보느냐는 문제다. 검색 agent는 최신 웹 결과를 보고, 코드 분석 agent는 저장된 문맥을 보고, 실행 agent는 방금 tool이 돌려준 출력을 본다. 이 셋이 제각각의 기억을 들고 움직이면 시스템은 곧 모순된 결론을 낸다. reactive라는 이름 아래 사실상 분산된 혼란이 생기는 셈이다.
그래서 shared agentic environment가 중요하다. environment는 단순한 메모리 저장소가 아니다. 어떤 사실이 언제 생성되었고, 어느 agent가 썼으며, 어떤 tool 결과에 근거했는지, 아직 유효한지, 다른 사실과 충돌하는지까지 포함하는 공용 작업면에 가깝다. agent는 이 환경을 읽고 쓴다. 지시를 받기 전에 이미 상황을 보고, 필요하면 스스로 반응한다.
이 모델은 특히 context engineering에서 힘을 발휘한다. 많은 시스템이 context를 “프롬프트에 얼마나 많이 넣을까”의 문제로 다룬다. 하지만 multi-agent 환경에서는 양보다 흐름이 먼저다. 어느 정보가 누구에게, 어떤 시점에, 어떤 압축 수준으로 전달되느냐가 성능을 좌우한다. shared environment는 context를 문장 묶음이 아니라 상태와 이벤트의 관계로 다루게 만든다. 그 결과 agent는 매번 긴 대화를 통째로 넘겨받지 않아도 되고, 바뀐 부분만 보고 반응할 수 있다.
반응성은 빠르지만, 쉽게 시끄러워진다
이 구조가 주는 첫 번째 이점은 지연을 줄인다는 점이다. 중앙 coordinator가 모든 단계를 순서대로 관장하면 병렬성이 제한된다. 반면 이벤트 기반 구조에서는 검색과 분류, 추적과 검증 같은 작업이 서로를 기다리지 않고 달릴 수 있다. 특히 TypeScript처럼 비동기 처리와 event model이 익숙한 언어에서는 이 장점이 구현으로 바로 이어진다. Promise, stream, queue, AbortSignal, typed event emitter 같은 도구를 조합하면 반응적 runtime을 비교적 자연스럽게 만들 수 있다.
하지만 반응성은 쉽게 소음으로 변한다. 이벤트가 많아질수록 시스템은 바빠 보이지만 실제 진전은 느려질 수 있다. 한 agent의 업데이트가 다른 agent를 깨우고, 그 agent의 판단이 다시 세 번째 agent를 깨우면서 불필요한 연쇄 반응이 생긴다. 흔히 “agent가 활발하게 일하고 있다”고 보이는 순간이 사실은 가장 위험하다. 이벤트 수는 늘어나는데 확정된 결과물은 늘지 않는 상태, 같은 주제를 여러 agent가 반복해서 되짚는 상태, 취소된 작업이 environment에 찌꺼기로 남아 후속 판단을 오염시키는 상태가 나오기 쉽다.
이 문제를 막으려면 reactive framework는 단순한 pub/sub를 넘어야 한다. 이벤트는 타입이 있어야 하고, 상태 전이는 명시적이어야 하며, 중복 반응을 막는 idempotency 규칙이 필요하다. agent가 같은 사실에 대해 여러 번 반응하더라도 결과가 폭주하지 않도록 설계해야 한다. 결국 reactive architecture의 품질은 agent의 언어 능력보다 runtime의 절제력에서 갈린다.
TypeScript가 여기서 유난히 잘 맞는 이유
TypeScript는 AI agent 프레임워크를 만들기에 자주 선택되는 언어인데, 그 이유를 “생태계가 넓어서” 정도로만 설명하면 절반만 맞다. 더 중요한 이유는 경계의 형태를 다루기 좋다는 데 있다. multi-agent 시스템의 진짜 문제는 알고리즘보다 경계다. 어떤 이벤트가 유효한지, 어떤 tool 결과가 신뢰 가능한지, 어떤 상태에서 어떤 action이 허용되는지 같은 경계를 명확히 해야 한다. TypeScript의 장점은 이 경계를 타입으로 설명할 수 있다는 점이다.
예를 들어 agent가 반응하는 이벤트를 막연한 문자열이 아니라 discriminated union으로 정의하면, 런타임 설계가 훨씬 단단해진다.
type AgentEvent = | { type: "search.completed"; query: string; sources: string[] } | { type: "tool.failed"; tool: string; reason: string; retryable: boolean } | { type: "context.updated"; keys: string[]; version: number }; function react(event: AgentEvent) { switch (event.type) { case "search.completed": return scheduleVerifier(event.sources); case "tool.failed": return event.retryable ? backoffRetry(event.tool) : escalate(event.reason); case "context.updated": return maybeRefreshPlan(event.version); } }
이런 타입 정의가 대단한 미학을 주는 것은 아니다. 대신 시스템을 오래 버티게 한다. reactive 구조에서 가장 흔한 장애는 “이벤트가 잘못 흘러도 아무도 모른다”는 것이다. TypeScript는 그 무관심을 조금 덜어 준다. 어느 이벤트가 소비되지 않는지, 어떤 상태 분기가 빠졌는지, tool 호출 결과가 기대한 모양이 아닌지 상대적으로 빨리 드러난다. AI 시스템이 불확실성을 품고 있다고 해서 프레임워크까지 흐릿해야 할 이유는 없다.
tool calling은 능력이 아니라 계약이다
agent 시스템을 이야기할 때 tool calling은 늘 화려하게 소비된다. 어떤 agent가 웹을 검색하고, 어떤 agent가 코드를 수정하고, 어떤 agent가 외부 API를 두드리는 장면은 데모에서 특히 강하다. 그런데 운영 환경으로 넘어가면 tool 호출은 능력보다 계약의 문제가 된다. 누가 호출할 수 있는가, 어떤 조건에서 호출하는가, 실패하면 어떤 신호를 남기는가, 결과는 누가 검증하는가가 더 중요해진다.
중앙 orchestration에서는 이 계약을 coordinator가 품는다. reactive 구조에서는 environment가 계약의 일부를 떠안아야 한다. tool 결과는 단순한 문자열이 아니라 provenance를 가진 사건이어야 한다. 성공했다는 사실뿐 아니라 입력 파라미터, 실행 시각, 재시도 여부, 비용, 실패 사유, 결과의 신선도까지 함께 다뤄져야 한다. 그래야 다른 agent가 그 결과를 재사용할지, 무시할지, 재검증할지 판단할 수 있다.
여기서 흔히 빠지는 함정이 있다. tool을 호출하는 agent에게 판단권까지 과하게 몰아주는 일이다. 검색 agent가 자료를 찾고, 그 자료의 신뢰도 평가까지 하고, 곧바로 최종 결론까지 밀어붙이면 reactive 시스템은 사실상 다시 단일 책임점으로 회귀한다. 반대로 너무 잘게 쪼개면 tool 결과가 environment 위를 끝없이 떠다니기만 하고 아무도 책임 있게 닫지 못한다. 좋은 구조는 tool 호출을 이벤트로 승격하되, 해석 책임과 실행 책임을 적절히 분리한다.
동시성은 성능의 문제가 아니라 해석의 문제다
AI agent 프레임워크에서 concurrency를 말하면 보통 처리량을 먼저 떠올린다. 더 많은 agent를 동시에 돌리면 더 빨라질 것처럼 보인다. 실제 병목은 속도보다 해석에서 자주 생긴다. 두 agent가 같은 문맥을 서로 다르게 읽고 동시에 업데이트를 올리면 무엇이 최신인가. 한 agent는 실패를 근거로 계획을 수정하는데, 다른 agent는 늦게 도착한 성공 신호를 보고 원래 계획을 계속 밀어붙이면 어느 쪽이 맞는가. 동시성은 CPU보다 의미를 먼저 충돌시킨다.
그래서 shared environment에는 storage 이상의 장치가 필요하다. 버전 관리, conflict resolution, stale read 방지, cancellation 전파 같은 메커니즘이 없으면 reactive 시스템은 금방 “논리적 race condition”에 빠진다. 특히 LLM 기반 agent는 응답 시간이 들쭉날쭉하고, 같은 문장을 받아도 판단이 약간씩 흔들리기 때문에 전통적인 concurrent program보다 더 미묘한 충돌을 만든다. 순서가 뒤바뀐 메시지 하나가 잘못된 확신으로 굳어질 수 있다.
운영에서 이런 문제는 대개 이상한 신호로 먼저 나타난다. 동일한 작업에 대해 계획 변경이 지나치게 잦아진다. 취소된 task가 계속 후속 action을 낳는다. 비슷한 tool 호출이 짧은 간격으로 반복된다. 응답 품질은 들쭉날쭉한데 토큰 사용량과 실행 비용만 오른다. 겉으로는 “agent가 많아져서 강해졌다”처럼 보이지만, 실제로는 context 경합이 커져서 판단의 일관성이 무너진 상태다.
중앙 통제가 사라지면 책임도 사라질까
reactive architecture를 비판하는 쪽에서는 종종 이렇게 묻는다. “누가 책임지는가?” coordinator가 없으면 디버깅이 더 어려워지는 것 아니냐는 걱정이다. 이 질문은 타당하다. 분산된 반응 시스템은 실패 원인을 추적하기 어렵다. 어떤 이벤트가 어떤 agent를 깨웠고, 그 반응이 어떤 tool 호출을 유발했으며, 그 결과가 왜 최종 결론으로 이어졌는지 보기 어렵다면 유지보수는 곧 악몽이 된다.
그래서 reactive framework는 orchestration을 버리는 대신 observability를 훨씬 더 강하게 가져가야 한다. 각 agent의 생각을 장황하게 기록하라는 뜻이 아니다. 중요한 것은 사건의 계보다. 어떤 event가 어떤 state transition을 만들었는지, 어떤 transition이 어떤 external action을 낳았는지, 왜 어떤 후보가 폐기됐는지를 구조적으로 남겨야 한다. 사람이 사후에 읽을 수 있을 정도의 추적 가능성이 없으면 “자율성”은 금세 “불투명성”의 다른 이름이 된다.
이 지점에서 좋은 프레임워크는 agent를 돋보이게 하지 않는다. 오히려 runtime을 보이게 한다. agent가 똑똑한 척하는 것보다, 시스템이 왜 그런 결정을 했는지를 복원할 수 있는 편이 훨씬 중요하다. 현장에서 필요한 것은 신비로운 자율성이 아니라 설명 가능한 반응성이다.
설계가 실패하는 순간은 대개 agent가 아니라 environment에서 시작된다
많은 팀이 multi-agent 시스템을 만들다가 비슷한 벽을 만난다. agent 프롬프트를 고치고, 모델을 바꾸고, tool을 더 붙여 보지만 품질이 안정되지 않는다. 원인을 파고들면 의외로 agent 자체보다 environment 설계가 약한 경우가 많다. 무엇이 사실인지, 무엇이 추정인지, 무엇이 임시 판단인지 구분되지 않는다. 오래된 context가 새 판단과 같은 무게로 섞인다. 반응의 우선순위가 없어 사소한 이벤트가 중요한 판단을 밀어낸다. 이런 구조에서는 어떤 모델을 얹어도 시스템이 차분해지기 어렵다.
공유 environment는 단순히 모두가 접근 가능한 저장소가 아니다. 의미의 교통정리 장치여야 한다. 읽기 쉬운 것보다 먼저 오염되기 어렵게 설계돼야 한다. 어떤 정보는 금방 만료되어야 하고, 어떤 정보는 검증 전까지 임시 영역에 머물러야 하며, 어떤 정보는 특정 agent만 승격시킬 수 있어야 한다. 말하자면 reactive framework의 중심은 agent가 아니라 사실의 생애주기다.
이 관점이 중요한 이유는, agent 수를 늘리는 일이 문제 해결처럼 보이기 쉽기 때문이다. 하지만 제대로 설계되지 않은 environment 위에 agent를 추가하는 일은 회의 참가자를 늘리는 것과 비슷하다. 발언은 많아지지만 결정은 느려진다. 반응성은 종종 속도의 이미지로 팔리지만, 실제 가치는 필요한 반응만 남기는 데서 나온다.
결국 더 강한 것은 지시받는 시스템이 아니라 감지하는 시스템이다
AI agent를 둘러싼 많은 논의가 아직도 “어떻게 더 똑똑하게 시킬까”에 머문다. 그러나 reactive framework가 던지는 더 본질적인 질문은 다르다. “어떻게 더 잘 반응하게 만들까”다. 지시 중심 구조는 평온한 상황에서 효율적이다. 해야 할 일이 정해져 있고, 예외가 드물고, context가 안정적일 때는 coordinator가 모든 것을 통제하는 편이 명확하다. 하지만 현실의 지식 작업은 점점 더 변동적이고, 비동기적이며, 외부 신호에 흔들린다. 이런 환경에서는 지시보다 감지가 먼저다.
TypeScript 기반 reactive AI agent framework가 가지는 잠재력도 여기에 있다. 이 접근은 agent를 독립적인 작은 두뇌로 분산시키는 데만 의미가 있지 않다. 공유된 environment 위에서 사건을 읽고, 상황이 바뀌면 스스로 재정렬하며, tool 호출과 context 흐름을 느슨하게 결합하는 runtime을 만들 수 있다는 데 의미가 있다. 중앙 orchestration의 시대가 끝난다는 이야기가 아니다. 다만 더 많은 시스템이 workflow에서 organism 쪽으로 이동하고 있다는 신호에 가깝다.
좋은 agent 시스템은 지시를 잘 따르는 시스템이 아니다. 변화가 생겼을 때 무엇을 다시 봐야 하는지 아는 시스템이다. 그 차이는 데모 화면에서는 잘 드러나지 않는다. 대신 운영 몇 주 뒤에 드러난다. 이벤트는 많지만 결과가 흐릿한 시스템과, 변화가 많아도 판단의 중심을 잃지 않는 시스템 사이의 차이로 드러난다. reactive architecture의 진짜 가치는 화려한 동시 실행이 아니라, 흔들리는 환경 속에서도 의미 있는 반응만 남겨 두는 데 있다.
댓글
댓글을 읽어오는 중입니다.
같이 읽으면 좋은 글
방금 읽은 주제와 이어지는 글을 골랐습니다.
한 번의 학습을 위해 서버를 갖지 않기로 했다
Gemma 4 같은 대형 open model을 다루는 순간 병목은 모델보다 운영이 된다. Cloud Run Jobs와 서버리스 GPU 조합은 실험성 fine-tuning을 더 가볍게 만들지만, multimodal 구조·LoRA 대상 선택·VRAM 관리 같은 새로운 함정을 함께 드러낸다.
GPU 클러스터 대신 Job 하나: Gemma 4 커스터마이징이 서버리스로 넘어가는 순간
Gemma 4 같은 대형 open model을 다루는 일은 더 이상 거대한 GPU 클러스터의 전유물이 아니다. Cloud Run Jobs와 RTX 6000 Pro 조합은 fine-tuning의 진입장벽을 낮추지만, 메모리 전략·LoRA 설정·체크포인트 운영 같은 실무 함정은 더 선명하게 드러낸다.
GPU 한 대로 끝내는 멀티모달 미세조정의 현실
Gemma 4와 serverless GPU 조합은 대형 멀티모달 모델 fine-tuning의 진입장벽을 낮춘다. Cloud Run Jobs, QLoRA, LoRA 타깃 전략, VRAM 관리까지 함께 짚으며 실전 적용 시의 기대와 함정을 균형 있게 풀어낼 글에 어울리는 메타데이터다.
이전 글
예쁜 코드보다 빨리 살아남는 UI가 필요한 순간
다음 글
작게 쪼갠 행동 계층: Needle를 볼 때 함께 비교해야 할 네 가지 tool-calling 운영 방식