콘텐츠로 이동

왜 좋은 소프트웨어는 AI 친화적인 소프트웨어이기도 한가

몇 년 전, 저는 AI와는 전혀 관계없는 아키텍처 문제를 마주하고 있었습니다.

하나의 제품이 멀티테넌트와 화이트라벨을 지원해야 했습니다. 최종적으로는 대략 일곱 개의 Client를 제공해야 했습니다. 기능적으로나 기술적으로 서로 매우 가까웠지만, 동시에 의도적으로 다른 부분들도 가지고 있었습니다. 가장 간단한 표현은 이랬습니다.

same, but different.

당시 저는 Angular 애플리케이션을 어떻게 모듈화하고 싶은지에 대해 이미 꽤 분명한 생각을 가지고 있었습니다. 부족했던 것은 여러 애플리케이션, 공유 Capability, 그리고 의도적으로 분리된 특성을 함께 담을 수 있는 지속 가능한 구조였습니다. 그렇지 않으면 일곱 개의 거의 독립적인 복사본이 생기거나, 아무도 더 이상 제대로 이해하기 어려운 설정 괴물이 만들어질 수 있었습니다.

그 구조를 찾는 과정에서 저는 거의 우연히 Nx를 알게 되었습니다. 꼭 Monorepo를 원해서도 아니었고, 당연히 Coding Agent를 위해 Repository를 최적화하려던 것도 아니었습니다. 오늘날과 같은 형태의 Coding Agent는 당시에는 존재하지도 않았습니다.

처음 제 관심은 아주 구체적인 제품 문제에 있었습니다. 그 이후에야 공유 Workspace가 가져오는 좀 더 일반적인 특성들을 보게 되었습니다. 애플리케이션, Libraries, 그리고 그 관계를 한곳에서 찾을 수 있었고, Tooling을 통일할 수 있었으며, Dependencies가 더 잘 보였고, 공통 기술 Capability를 의식적으로 공유할 수 있었습니다.

저에게는 지금도 의식적으로라는 부분이 중요합니다.

저는 Monorepo가 무엇보다도 최대한의 재사용을 위한 초대장이라는 생각에는 동의하지 않습니다. 하나의 코드베이스는 재사용을 쉽게 만듭니다. 하지만 추가적인 공유 Dependency가 아키텍처적으로 타당한지는 전혀 다른 문제입니다.

돌이켜보면, 오늘날에는 오히려 다른 점이 더 흥미롭게 느껴집니다.

오늘날 Coding Agent에게 중요해지고 있는 많은 특성은 원래 사람을 위해 우리가 만든 것입니다.

Monorepo는 그중 하나일 뿐입니다. 가시성, 공통 Toolchain, 재현 가능한 Build, 명확한 Dependencies, 자동화된 Tests는 원래 전형적인 Developer Experience 주제였습니다. Coding Agent는 여기에 두 번째 사용자를 추가합니다.

그렇다고 해서 큰 공유 Repository가 자동으로 Agent 친화적이 되는 것은 아닙니다.

모든 것이 한곳에 있다고 해서 그것이 곧 아키텍처는 아닙니다.

Repository에서 실행 가능한 작업 공간으로

섹션 제목: “Repository에서 실행 가능한 작업 공간으로”

저에게 더 흥미로운 변화는 나중에 왔습니다.

업무 환경에서는 오랫동안 매우 전형적인 분리를 경험했습니다. Frontend는 여기, Backend는 저기, 각각 별도의 Repository, Build Process, 개발 환경을 가지고 있었습니다. 두 영역은 정의된 Contracts를 통해 통신했습니다.

그 자체로는 완전히 타당한 구조입니다. Frontend를 로컬에서 테스트할 수 있었고, Backend도 로컬에서 테스트할 수 있었으며, Contract를 통해 두 쪽이 어떻게 통신해야 하는지도 이해할 수 있었습니다.

하지만 전체 시스템 Flow를 가로지르는 오류에서는 여전히 추가적인 경계가 생겼습니다. 문제는 Persistence에서 시작되어 Service에서 잘못 변환되고, API를 통해 전달된 뒤, 결국 Client에서만 드러날 수 있습니다. 기술적으로는 하나의 오류 시나리오이지만, 분석할 때 관련 요소들은 여러 작업 공간에 흩어져 있었습니다.

어느 순간부터 Nx에서 Repository 정리 자체보다 더 중요한 부분이 보이기 시작했습니다. Nx는 Project Graph를 통해 Projects와 그 관계를 알고 있습니다. Targets 또는 Tasks는 해당 Projects의 실행 가능한 Capability를 설명합니다. Task Pipelines와 dependsOn은 Tasks 사이의 Dependencies를 표현할 수 있고, run-many는 여러 Projects에 같은 표준화된 Tasks를 실행할 수 있습니다.

저에게 중요한 생각은 특정 Nx 버전에 묶여 있지 않습니다.

Workspace가 자신의 Dependencies를 알고 있다면, 파일을 정리하는 것 이상의 일을 할 수 있습니다. 시스템의 일부를 오케스트레이션할 수 있습니다.

처음에는 이것도 다시 전형적인 Developer Experience였습니다. 수동 시작 순서가 줄어들고, 어떤 명령을 어느 Project에서 실행해야 하는지에 대한 Project별 지식이 줄어들며, Workspace가 이미 알고 있는 관계를 위해 따로 작성해야 하는 Orchestration Logic도 줄어들었습니다.

개인적으로는 제 SCS 시스템에서 이 아이디어를 더 일관되게 밀어붙였습니다. 여러 도메인 시스템의 핵심 부분이 하나의 공유 코드베이스에 있습니다. 영역에 따라 Client, API, Service, 데이터베이스 관련 인프라, Libraries, Contracts, Container 설정, Tests, Infrastructure as Code가 포함됩니다.

CI 역시 이 시스템 지식을 사용합니다. Nx를 통해 영향을 받은 배포 가능한 Projects를 찾고, 관련 Docker Targets만 Build한 다음, 이를 구체적인 Stacks와 Services에 매핑합니다.

그렇게 되면 Repository의 의미가 달라집니다. 더 이상 시스템의 Code만 담는 곳이 아니라, 시스템의 중요한 부분을 Build하고, 시작하고, 검사하고, 운영하는 메커니즘까지 점점 함께 담게 됩니다.

시스템 자체가 실행 가능한 작업 공간이 됩니다.

처음에는 이 변화가 Agentic Work와 아무 관련이 없었습니다. 바로 그렇기 때문에 지금은 더 흥미롭게 느껴집니다.

시스템 전체를 이해하되, 변경은 로컬하게

섹션 제목: “시스템 전체를 이해하되, 변경은 로컬하게”

실행 가능한 작업 공간이 있다고 해서 Coding Agent에게 데이터베이스에서 View까지 전체 시스템을 한 번에 관통하는 하나의 거대한 작업을 준다는 뜻은 아닙니다.

Requirements가 충분히 명확하다면 Agent가 그런 End-to-End 변경도 구현할 수 있을 것입니다. 그래도 저는 오히려 반대로 접근합니다. 실제 작업 패키지는 가능한 한 작게 유지하려고 합니다.

공유 시스템이 가장 크게 바꾸는 것은 분석입니다.

오류를 View에서 Backend Service까지, 혹은 Persistence까지 추적해야 한다면 Agent의 시야 안에 인위적인 경계를 가능한 한 적게 두고 싶습니다. Contract가 바뀌면 그에 의존하는 Code도 접근 가능해야 합니다. E2E Test가 실패했는데 다음 관련 Layer가 우연히 다른 Repository에 있다는 이유로 분석이 끝나서는 안 됩니다.

구현 단계에서는 다른 규칙을 적용합니다.

넓은 시야, 좁은 변경 범위: 분석에서는 Agent가 관련 Flow를 시스템 전체에서 이해할 수 있어야 하지만, 실제 구현은 작은 자율 작업 공간으로 제한되어야 합니다.

저에게는 이것이 모순이 아닙니다.

Agent는 시스템 전체를 이해할 수 있지만, 시스템 전체를 변경할 필요는 없습니다.

이 시리즈의 10번째 글은 열린 의사결정 공간을 다뤘습니다. 어디에서 탐색이 필요한지, 어디에서 결정을 내려야 하는지, 그리고 언제 구현이 최대한 지루해져야 하는지를 살펴봤습니다. 여기서는 비슷하지만 다른 질문을 다룹니다. 작은 구현 작업이라고 해서 Agent에게 주변 시스템에 대한 모든 시야를 인위적으로 제거할 필요는 없습니다. 특히 분석과 검증에서는 더 넓은 시야가 가치가 있을 수 있습니다.

반대로 Agent가 이론적으로 전체 시스템에 접근할 수 있다고 해서 거대한 구현 작업이 더 좋아지는 것도 아닙니다.

아키텍처는 두 가지를 모두 가능하게 해야 합니다. 넓은 시야와 좁은 변경 범위.

하지만 더 넓은 시야도 다른 문제 하나는 해결하지 못합니다. Repository에는 팀이 시스템에 대해 알고 있는 모든 지식이 자동으로 들어 있지 않습니다.

사람은 이 문제를 놀라울 정도로 잘 보완합니다. 어떤 개발자는 한 시스템에서 5년 동안 일하면서, 특정 Class가 올바른 진입점처럼 보이지만 새 기능에는 더 이상 사용하면 안 된다는 사실을 알고 있을 수 있습니다. 어떤 Service가 역사적으로 잘못된 위치에 있다는 것도 알고, Code에서 거의 드러나지 않는 Dependency도 알고, 두 개의 기존 Pattern 중 더 자주 보이는 쪽이 사실은 더 이상 사용하면 안 되는 오래된 방식이라는 것도 압니다.

때로는 가장 솔직한 설명이 이것뿐입니다.

어디에도 적혀 있지 않습니다. 그냥 우리가 알고 있습니다.

소프트웨어 개발은 지식 집약적인 활동이며, Software Maintenance, Tacit Knowledge, Architecture Decision Knowledge에 대한 연구는 도메인, 아키텍처, 역사적 결정에 관한 중요한 지식이 종종 불완전하게만 문서화된다는 점을 오래전부터 설명해 왔습니다. Coding Agent는 이 근본적인 문제를 바꾸지 않습니다.

Agent는 오랜 세월 팀 구성원들의 머릿속에 쌓인 조직 특유의 역사를 자동으로 갖고 있지 않을 뿐입니다.

처음 Agent가 활용할 수 있는 것은 접근 가능하고 해석 가능한 것들입니다. Code, Repository Structure, Documentation, Contracts, Tests, Build Configuration, Agent Files, Static Rules, Version History, Logs, Executable Tools입니다.

최근 Coding Agent 연구는 올바른 Repository Context를 찾는 것 자체가 독립적인 어려움임을 보여줍니다. Agent Retrieval Bench는 이 단계를 따로 분리해 분석하며, 기록된 Agent Trajectories 중 상당한 비율에서 관련 있다고 표시된 Files를 모두 놓치는 경우가 있음을 보여줍니다. ContextBench는 같은 메커니즘을 다른 방향에서 살펴보고, 탐색한 Context와 실제로 사용한 Context 사이에 측정 가능한 차이가 있음을 보여줍니다.

그렇다고 모든 경험 지식을 문서화해야 한다는 뜻은 아닙니다. 현실적이지도 않고 유용하지도 않습니다. 다만 중요한 규칙과 관계를 시스템 자체에서 더 많이 추론할 수 있을수록, Agent에게 시스템이 덜 수수께끼처럼 보인다는 뜻입니다.

Hidden Knowledge와 명시적 시스템 지식의 비교: 팀의 머릿속에만 존재하는 지식은 Agent가 자동으로 사용할 수 없지만, Contracts, Tests, Boundaries, 문서화된 결정은 접근 가능하며 일부는 기계적으로 검증할 수 있습니다.

명시적인 Contracts는 새 개발자에게 도움이 됩니다. Agent에게도 도움이 됩니다. 아키텍처 규칙은 팀에 도움이 됩니다. Agent에게도 도움이 됩니다. 재현 가능한 Build는 월요일 아침 새 Laptop을 설정하는 개발자에게 도움이 됩니다. 자기 변경을 검증해야 하는 Agent에게도 도움이 됩니다.

바로 이 지점에서 오래된 소프트웨어 엔지니어링 아이디어가 새로운 사용자를 만납니다.

일관성, 표준, 머신이 읽을 수 있는 구조

섹션 제목: “일관성, 표준, 머신이 읽을 수 있는 구조”

저는 일관성을 꽤 강하게 선호합니다. 덜 외교적으로 말하면 이렇습니다.

일관성은 제 신조입니다.

도메인을 나눌 때 저는 구조적으로 비슷하게 읽히기를 원합니다. 완전히 같을 필요는 없습니다. 서로 다른 도메인 문제는 서로 다른 해결책을 필요로 할 수 있습니다. 하지만 익숙한 Layers는 같은 의미를 가져야 하고, 반복되는 Patterns는 다시 알아볼 수 있어야 하며, Commands는 이유 없이 달라지면 안 되고, Lint Rules는 중앙에서 적용되어야 하며, Tooling Configuration은 로컬한 예외를 가능한 한 적게 만들어야 합니다.

이것은 제가 Coding Agent를 진지하게 사용하기 훨씬 전, Tech Lead였을 때부터 가지고 있던 태도였습니다.

흥미롭게도 저는 이제 Agent가 사람과 똑같은 방식으로 이런 일관성을 필요로 한다고 말하는 데 더 조심스럽습니다. 사람은 반복되는 구조를 통해 방향을 잡는 경향이 강합니다. 열 개의 도메인 영역이 비슷하게 구성되어 있다면 열한 번째 영역에 들어갈 때의 인지 비용이 낮아집니다. 반면 Agent는 다른 아홉 개를 한 번도 보지 않고도 하나의 영역을 충분히 분석할 수 있습니다.

그리고 사실 그것이 바람직합니다.

아키텍처가 Agent에게 Repository 전체를 이해할 필요가 없도록 만든다면, Agent는 Repository 전체를 이해할 필요가 없습니다.

그래도 일관성은 가치가 있습니다. 관련 영역 안에서 기대를 안정시키고, 일반적인 검사를 가능하게 하며, Build와 Test Logic에 필요한 특수 지식을 줄이고, 명시적 규칙이 시스템 전체에서 실제로 적용될 가능성을 높입니다.

여기서 제 두 번째 신조가 나옵니다.

Stick to the standards.

저는 Nx에서 구조가 도구가 의도한 메커니즘에서 불필요하게 멀어지면 어떤 일이 생기는지 직접 경험했습니다. 커스텀 Executor Logic, 특수 설정, 개별 Project Structure는 단기적으로는 영리해 보일 수 있습니다. 하지만 이후 Upgrade에서는 그 특수 지식까지 함께 Migration해야 합니다.

이것은 모든 Default를 무비판적으로 받아들이자는 주장이 아닙니다. Framework와 Tooling의 Standards는 자연법칙이 아닙니다. 더 흥미로운 Engineering Rule은 이쪽입니다.

불필요한 모든 일탈은 추가적인 특수 지식을 만듭니다.

사람에게도, Tooling에게도, 그리고 점점 Agent에게도 그렇습니다.

그래서 이 지점에서는 Nx를 조금 칭찬해도 괜찮다고 생각합니다. 모든 소프트웨어 시스템을 Nx Monorepo로 만들자는 뜻도 아니고, Nx가 AI Platform이라고 주장하는 것은 더더욱 아닙니다.

제가 Nx에서 흥미롭게 보는 것은, 전통적인 소프트웨어 엔지니어링 아이디어가 어떻게 명시적이고, 조회 가능하며, 실행 가능하게 되는지를 보여주는 구체적인 사례라는 점입니다. Workspace는 Projects로 구성됩니다. Project Graph는 관계를 설명합니다. Tags는 의미론적 카테고리를 표현할 수 있습니다. Targets 또는 Tasks는 실행 가능한 Capability를 설명합니다. Task Pipelines는 Tasks 사이의 Dependencies를 설명합니다. run-many는 여러 Projects에 표준화된 Tasks를 실행합니다. affected는 Git Diff와 Project Graph를 결합해 Workspace에서 잠재적으로 영향을 받는 부분을 계산합니다.

Graph 자체도 단순한 시각화가 아닙니다. Nx는 Project Graph와 Task Graph 데이터를 내보내거나 구체적인 실행에 대해 이를 노출할 수 있습니다. 그 결과 Repository Topology의 일부가 머신이 읽을 수 있는 구조가 됩니다.

제가 Nx를 좋아하는 이유는 모든 시스템이 Monorepo여야 해서가 아닙니다. Repository Topology의 중요한 부분이 명시적이고, 조회 가능하며, 검증 가능해질 수 있다는 아이디어가 좋습니다.

사람에게는 좋은 Developer Experience입니다. Agent에게는 접근 가능한 구조가 됩니다.

저는 Nx를 거의 처음부터 Module Boundaries와 함께 사용해 왔습니다. 여기서도 원래의 동기는 AI와 무관했습니다.

예를 들어 UI-specific Libraries가 갑자기 Business Layer에서 사용되는 것을 막고 싶었습니다. 도메인 영역이 서로의 내부 구현을 마음대로 Import해서는 안 됐습니다. Dependency Direction이 Diagram에만 존재해서도 안 됐습니다.

JavaScript와 TypeScript Projects를 위해 Nx는 @nx/enforce-module-boundaries를 제공합니다. Projects는 Tags로 분류할 수 있고, Dependency Constraints를 통해 어떤 영역이 어떤 카테고리에 접근할 수 있는지, 혹은 어떤 Dependencies가 금지되는지를 정의할 수 있습니다. 전체 Project Graph에 대한 언어 간 규칙에는 Conformance Rules도 제공합니다.

8번째 글에서는 이런 메커니즘이 구조적으로 허용되는 해결 공간을 왜 제한하는지 다뤘습니다. 여기서 제가 관심 있는 것은 다른 효과입니다. 이 규칙들이 Feedback을 제공한다는 점입니다.

Agent가 Code를 변경합니다. 이후 Lint가 실행됩니다. 그러면 아키텍처가 구체적인 위반에 답할 수 있습니다.

이 Import는 허용되지 않습니다.

이 시점에서 Agent는 제 아키텍처 의도를 완벽하게 해석할 필요가 없습니다. 자기 변경 결과에 대한 기계적 Feedback을 받습니다.

기계적으로 실행 가능한 아키텍처는 Agent에게 자기 작업이 어디에서 경계를 위반했는지 알려줄 수 있습니다.

이것이 Wiki Page에만 존재하는 규칙과, 위반 시 실패한 Task를 만들어 내는 규칙의 차이입니다. Documentation은 의도를 설명합니다. 실행 가능한 Rule은 결과의 일부를 검사합니다. 둘 다 필요합니다.

affected: Scope를 실행 가능하게 만들기

섹션 제목: “affected: Scope를 실행 가능하게 만들기”

Nx에서 제가 특히 강력하다고 생각하는 개념 중 하나는 affected입니다.

핵심 동작은 단순합니다. Nx는 Git을 통해 변경된 Files를 찾고, Project Graph를 통해 이를 Projects에 매핑한 다음, 해당 Projects에 의존하는 Projects도 고려합니다. 따라서 Test, Lint, Build 같은 Tasks를 그 부분집합으로 제한할 수 있습니다.

바로 여기서 affected는 저에게 아키텍처적으로 흥미로워집니다.

모든 것이 모든 것에 의존한다면 항상 모든 것이 affected입니다. 형식적으로 Project Graph는 여전히 존재하지만, Scope Limiter로서의 실질적인 가치는 크게 떨어집니다.

affected가 정말 강력하려면 시스템은 어차피 우리가 원해야 하는 특성을 가져야 합니다. Autonomy, 깨끗한 Dependencies, 제한된 Change Radius, 그리고 의식적으로 설계된 Boundaries입니다.

제 개인 시스템에서 affected는 이제 단순한 CI Optimization이 아닙니다. Pipeline은 docker:build Target을 통해 affected Projects를 찾고, Build와 Deployment를 그 결과에 해당하는 Services 또는 Stacks로 제한합니다.

Agentic Work에서는 여기에 또 하나의 해석이 추가됩니다.

Affected를 검증 Scope로 보기: Dependencies가 깔끔하게 분리되어 있다면 작은 변경은 작은 affected Graph로 이어지고, 그 결과 검증해야 할 공간도 더 작아집니다.

이것은 명백히 보편적인 아키텍처 Metric이 아닙니다. 중앙 Library가 많은 Consumers를 가지는 것이 정당한 경우도 있습니다. 어떤 변경은 도메인상 시스템 전체에 영향을 줍니다. 잘못된 Dependency Graph는 단지 거짓된 안전감만 줄 수도 있습니다.

그래도 Engineering Signal로서 이 Scope는 흥미롭습니다.

작은 affected Scope는 단지 더 빠른 것이 아닙니다. 변경을 로컬하게 이해하고 로컬하게 검증할 수 있다는 신호일 수도 있습니다.

따라서 저에게 affected는 동시에 CI Optimization, Scope Detector, Verification Aid가 됩니다.

이 지점에서는 전형적인 Monorepo 반사가 등장합니다. 모든 것이 함께 접근 가능하니, 이제 드디어 모든 것을 재사용할 수 있지 않을까?

기술적으로는 맞습니다. 아키텍처적으로는 위험한 결론이라고 생각합니다.

도메인 재사용에 대해서는 매우 신중하게 살펴봅니다. 두 영역이 실제로 같은 도메인 개념, 같은 Responsibility, 같은 Change Reason을 가진다면 하나의 공통 Owner가 의미 있을 수 있습니다.

순수한 기술적 재사용에는 훨씬 더 조심스럽습니다. OAuth Guard처럼 실제로 공통 기술 Capability를 나타내는 것은 자체 Library가 적절할 수 있습니다. 하지만 우연히 거의 똑같이 생긴 Mapper 두 개만으로는 원래 자율적인 영역을 서로 결합할 이유가 되지 않습니다.

저는 세 줄의 Code를 아끼려고 두 자율 영역 사이에 새로운 Dependency를 도입하느니 Mapper를 두 번 작성하는 편을 택하겠습니다.

이것은 재사용을 반대하는 절대 규칙이 아닙니다. 반사적인 DRY보다 Autonomy를 우선하는 것입니다.

Vaughn Vernon은 이 맥락에서 DRY가 동일한 Code Line을 피하는 것과 쉽게 혼동되지만, 그 배경의 생각은 더 강하게 중복된 지식에 관한 것이라고 지적합니다. 제 주장에는 더 조심스러운 결론만으로 충분합니다. 비슷해 보이는 Code가 자동으로 같은 Model을 의미하지 않으며, 새로운 Dependency를 추가할 좋은 이유가 되는 것도 아닙니다.

Monorepo는 Shared-Code Festival이 아닙니다. 공유 코드베이스는 재사용을 가능하게 합니다. 언제 그것이 의미 있는 Shared Capability가 되고, 언제 단지 새로운 Coupling을 만드는지는 여전히 아키텍처가 결정합니다.

Developer Experience에서 Agent Infrastructure로

섹션 제목: “Developer Experience에서 Agent Infrastructure로”

여기까지 읽으면 이 글이 주로 Monorepo와 Nx에 관한 글처럼 보일 수 있습니다. 하지만 그건 핵심을 너무 좁게 보는 것입니다. Nx는 단지 특히 구체적이고 이해하기 쉬운 예입니다.

진짜 생각은 전체 개발 환경을 바라볼 때 드러납니다.

우리는 수년 동안 재현 가능한 로컬 환경, Containers, 표준화된 Start Commands, Unit Tests, Integration Tests, E2E, Lint, Formatting, Type Checks, Build Targets, Dependency Graphs, Architecture Rules, Infrastructure as Code, CI Pipelines에 많은 노력을 투자해 왔습니다.

원래의 사용자는 개발자였습니다.

좋은 로컬 환경은 새 팀원이 손으로 작성된 Setup 설명을 해석하느라 사흘을 보내지 않도록 하기 위한 것이었습니다. 표준화된 Build는 재현 가능해야 했고, Tests는 Feedback을 제공해야 했으며, Lint는 단순한 오류와 Rule Violation을 자동으로 잡아야 했고, Containers는 환경 차이를 줄여야 했으며, CI는 관련 검사를 신뢰성 있게 반복해야 했습니다.

이 모든 것이 Developer Experience였습니다.

그리고 이 인프라에 새로운 사용자가 추가되었습니다.

오늘날 Coding Agent는 같은 메커니즘을 직접 사용할 수 있습니다.

Developer Experience가 Agent Infrastructure가 됩니다: Agent는 변경을 수행하고, Build, Lint, Tests를 실행하며, 관련 Services를 시작하고, E2E를 실행하고, 오류를 읽고, 수정한 뒤 다시 검증합니다.

이것은 단순한 이론적 제품 아이디어가 아닙니다. SWE-agent, SWE-Gym, SWE-bench 같은 Software Engineering Benchmarks와 Agent Environments는 Repository를 탐색하고, Files를 수정하고, 생성된 변경을 실행 가능한 Runtime과 Test Environment에 대해 검증할 수 있다는 능력을 핵심으로 사용합니다.

그렇다고 이것만으로 제 Repository에 대한 보편적인 생산성 주장을 만들 수는 없습니다. 하지만 기본 메커니즘은 분명합니다. Coding Agent가 Text를 생성하는 것뿐 아니라 실행 가능한 소프트웨어 환경과 상호작용할 수 있다면 훨씬 더 유용해집니다.

과거 Developer Experience였던 것이 점점 Agent Infrastructure가 되어 갑니다.

Agent-readable만으로는 충분하지 않다

섹션 제목: “Agent-readable만으로는 충분하지 않다”

저에게 이것은 아마 가장 강한 결론입니다.

AI 친화적인 Repository를 이야기하면 우리는 곧 Context로 향합니다. 좋은 Documentation, Agent Files, 명확한 파일 이름, Repository Maps, Architecture Overviews. 모두 중요합니다. Agent는 관련 정보를 찾을 수 있어야 합니다.

하지만 Repository가 매우 읽기 쉬워도 형편없는 Agentic Workspace일 수 있습니다.

Documentation이 아주 잘 된 시스템을 상상해 봅시다. Agent는 아키텍처를 이해하고, 올바른 Service를 찾고, 관련 Contract를 확인한 뒤, 그럴듯한 Patch를 작성합니다.

그 다음에는요?

Build는 한 동료의 컴퓨터에서만 작동합니다. Tests는 사실상 없습니다. 로컬 Backend Stack은 몇 달째 시작되지 않습니다. E2E란 누군가 Test Environment에서 수동으로 다섯 번 클릭하는 것을 의미합니다. 하위 Projects마다 Lint Rules가 다릅니다. 가장 중요한 아키텍처 Rule은 Diagram 하나에만 존재합니다.

Agent는 자신의 가설을 아주 잘 설명할 수 있습니다. 하지만 거의 검증할 수 없습니다.

따라서 AI 친화적이라는 것은 agent-readable만을 의미하지 않습니다. agent-verifiable이어야 합니다.

좀 더 일반적으로 말하면:

AI 친화적인 소프트웨어는 Agent에게 Context만 주는 것이 아니라 Feedback도 제공합니다.

더 큰 Context Window는 더 많은 Files를 담을 수 있습니다. 하지만 작동하는 Test를 만들지는 않습니다. 더 나은 Repository Search는 올바른 Service를 찾을 수 있습니다. 하지만 깨진 로컬 Build를 재현 가능하게 만들지는 않습니다. 훌륭한 Agent File은 Presentation이 Infrastructure에 의존하면 안 된다고 설명할 수 있습니다. 기계적인 Boundary Rule은 그 구체적인 위반을 추가로 거부할 수 있습니다.

이제 좋은 Agentic Workspace를 판단하는 질문도 달라집니다. 단지 *Agent가 얼마나 많은 Repository Context를 볼 수 있는가?*가 아니라, *“이 변경이 맞다고 생각한다”에서 신뢰할 수 있는 검증 결과까지 얼마나 안정적으로 갈 수 있는가?*도 중요합니다.

Feedback Loop는 작업 공간의 일부다

섹션 제목: “Feedback Loop는 작업 공간의 일부다”

제 실제 작업에서는 Coding Agent가 이런 메커니즘을 직접 사용하게 합니다.

변경 후 Agent가 단지 Diff만 보여주기를 원하지 않습니다. 작업에 따라 Unit Tests를 실행하고, Lint를 돌리고, 관련 Projects를 Build하고, 적절한 Feature에서는 필요한 Stack을 시작하고, E2E를 실행하고, Failure를 읽고, 수정한 다음 다시 검증합니다.

이 단계 중 어느 것도 완전한 기능적 정확성을 증명하지 않습니다. Green Build는 우리가 올바른 Feature를 만들었는지 알려주지 않습니다. Unit Test는 잘못된 가정을 완벽하게 보호할 수도 있습니다. E2E Test는 우리가 실제로 정의한 Scenario만 검사합니다. Module Boundaries는 모든 나쁜 아키텍처 결정을 막지 못합니다.

중요한 것은 서로 다른 Feedback Channels의 조합입니다. Build가 답하는 질문과 Lint가 답하는 질문은 다릅니다. Architecture Rules는 Unit Tests와 다른 질문에 답합니다. E2E는 Type Checks와 다른 질문에 답합니다. Human Review는 모든 자동화 메커니즘과 다른 질문을 다룹니다.

Agent가 결과를 저에게 가져오기 전에 이런 Channel을 가능한 한 많이 직접 사용할 수 있을수록, 저에게 Agentic Work는 더 견고해집니다.

그에 따라 제 Review의 중심도 이동합니다. Agent가 이미 스스로 Build하고, Lint하고, Test했다면 저는 다음과 같은 질문에 더 집중할 수 있습니다. Responsibility가 올바른 위치에 있는가? 원하지 않는 새로운 Coupling이 생기는가? 도메인 Boundary가 유지되는가? 해결책이 이전에 내린 Architecture Decision과 맞는가? Patch가 구조적 Drift를 만드는가? 이 해결책은 적절한가, 아니면 단지 기술적으로 동작할 뿐인가?

Agent는 점점 기능과 기술적 일관성을 스스로 검증하고, 제 Review는 더 강하게 아키텍처와 결정에 초점을 옮깁니다.

여기서 더 강하게가 중요합니다. 저는 여전히 기능도 Review하고, Green Test Run을 맹신하지 않으며, 아키텍처를 Review Model에 넘기지도 않습니다. 10번째 글에서는 왜 강한 모델을 추가적인 Review Channel로 자주 사용하는지도 설명했습니다. 저에게 Human Review와 Model Review는 서로를 대체하지 않습니다. 실행 가능한 인프라는 둘 모두를 보완합니다.

그리고 현재 저는 최소 Runtime을 최우선으로 최적화하지도 않습니다. 아주 빠른 Green Signal과 완전하고 추적 가능한 Report 중 하나를 선택해야 한다면 Report를 선택합니다.

저는 Tests가 Green이었는지만 궁금한 것이 아닙니다. 어떤 Projects가 영향을 받았는지, 무엇을 Build했는지, 어떤 Tests가 실행되었는지, 어떤 Architecture Rules가 검사되었는지, 무엇이 Skip되었는지, 어떤 Warning이 나왔는지, 어떤 Failure가 처음에 있었고 무엇을 수정했는지도 궁금합니다.

이 결과는 이후 두 번째 기능을 갖습니다. 그 자체가 다시 Context가 됩니다. 저는 Report를 검토할 수 있고, 추가적인 Review Model에 제공해 예를 들어 이번 변경에 대한 검증이 충분했는지, 어떤 위험이 아직 다뤄지지 않았는지 물어볼 수 있습니다.

빠른 Feedback은 편리합니다. 신뢰할 수 있는 Feedback이 중요합니다.

깨끗한 Boundaries와 affected는 어차피 Scope를 줄입니다. Parallelization이나 Subagents는 Runtime을 더 줄일 수 있지만, 다른 비용 문제를 열게 됩니다. 그것은 이후 글에서 다룰 주제입니다.

지금까지의 주장을 세 가지 특성으로 압축해야 한다면 저는 이 세 가지에 도달합니다. 새로운 “AI-ready Repository Checklist”가 아니라, 제 실무에서 잘 작동하는 것을 설명하는 표현입니다.

명시적: 중요한 구조는 가능한 한 시스템에 드러나야 합니다. Projects, Dependencies, Contracts, Layers, Commands, Tests, Architecture Rules, 그리고 나중에 Code만으로 그 이유를 안정적으로 재구성하기 어려운 결정은 문서화되어야 합니다. 일반적인 변경 하나를 위해 Agent가 몇 년간의 팀 역사를 추측해야 해서는 안 됩니다.

제한적: 시스템 전체에 대한 접근성은 있을 수 있습니다. 그래도 구체적인 작업은 작은 작업 공간을 가져야 합니다. 다른 다섯 개의 Domain이 같은 Repository에 있다는 이유만으로 한 Domain이 그들에 대해 알아야 할 필요는 없습니다.

검증 가능: Agent는 Build, Lint, Tests, Architecture Rules, 재현 가능한 Runtime, 그리고 적절한 곳에서는 E2E를 통해 신뢰할 수 있는 Feedback을 받아야 합니다.

저에게는 이것이 꽤 정확한 정의가 됩니다.

AI 친화적인 소프트웨어는 관련 지식을 명시적으로 만들고, 구체적인 작업 공간을 작게 유지하며, Agent가 자신의 변경을 검증할 수 있게 합니다.

흥미롭게도 이 특성 중 어느 것도 AI를 위해 발명된 것은 아닙니다.

Monorepo는 인프라이지 Agent 전략이 아니다

섹션 제목: “Monorepo는 인프라이지 Agent 전략이 아니다”

이제 글의 시작점으로 돌아갈 수 있습니다.

2백만 줄의 Big Ball of Mud를 가진 Repository가 모든 Files를 같은 Git Checkout에 넣었다고 해서 Agent 친화적이 되지는 않습니다. Agent가 이제 모든 것에 접근할 수 있을지는 모릅니다. 하지만 그중 얼마나 많은 것을 실제로 이해해야 하는지는 여전히 별개의 문제입니다.

큰 구조화 Repository와 큰 해석 공간의 비교: 명확한 도메인 Boundaries, 명시적인 Dependencies, 작은 로컬 작업 공간은 시스템 전체에 접근하면서도 Scope를 제한할 수 있게 합니다. 반대로 Global Utilities, 불분명한 Ownership, Cross-Dependencies, 경쟁하는 Patterns는 큰 해석 공간을 만듭니다.

다른 Repository는 같은 크기여도 구조가 완전히 다를 수 있습니다. 중요한 것은 Checkout의 크기가 아니라, 구체적인 변경을 위해 그중 얼마나 많은 부분을 실제로 이해해야 하는가입니다.

그래서 저는 Monorepo가 Agent를 자동으로 더 좋게 만든다고 절대 쓰지 않을 것입니다. 그 주장에는 명확한 인과 관계도 없고, 보편적인 아키텍처 규칙도 없습니다.

제 주장은 더 작습니다.

Monorepo는 인프라입니다. Agent 친화성은 구조에서 나옵니다.

하나의 공유 Workspace는 시스템 전체 분석과 Orchestration을 쉽게 만들 수 있습니다. 그것이 좋은 Agentic Workspace가 되는지는 구조가 결정합니다.

Big Ball of Mud에는 무슨 일이 생길까?

섹션 제목: “Big Ball of Mud에는 무슨 일이 생길까?”

여기서 불편한 반대 질문이 생깁니다.

이 사이트에서 저는 Big Ball of Mud를 장기적인 아키텍처와 경제성 문제로 이미 자세히 다뤘습니다. Coding Agent는 여기에 무언가를 바꿀 수 있습니다. 다만 처음 예상하는 방향과 다를 수 있습니다.

제 현재 개인적인 가설은 이렇습니다. Coding Agent는 Big Ball of Mud에서도 놀라울 정도로 오랫동안 생산적으로 작업할 수 있을지도 모릅니다. 어쩌면 사람보다 더 오래.

Agent는 매우 많은 Code를 검색하고, 반복되는 구조를 추적하고, 깊은 Call Chain을 재구성하고, 같은 관계를 몇 번이고 다시 분석할 수 있습니다. 사람이 이런 시스템에서 인지적·조직적 한계에 부딪힌다고 해서 Agent도 같은 지점에서 실패한다는 뜻은 아닙니다.

최근 Repository Retrieval 연구는 대형 Codebase와 Context Acquisition이 여전히 어려운 문제임을 분명히 보여줍니다. 하지만 Agent가 비구조적인 시스템에서 반드시 즉시 실패한다고 보여주지는 않습니다.

그래서 다음과 같은 단순한 주장은 저에게 너무 편합니다.

나쁜 아키텍처는 AI와 함께 작동하지 않는다.

아마 놀라울 정도로 오랫동안 작동할 것입니다.

더 흥미로운 질문은 이것입니다. 어떤 대가를 치르는가?

여기서 우리는 강한 실증적 주장 영역을 떠나 Engineering Hypothesis로 들어갑니다. 성장하는 Big Ball of Mud는 잠재적으로 관련 있는 정보의 공간을 넓힙니다. 더 많은 Dependencies를 조사해야 하고, 더 많은 역사적 Patterns를 비교해야 하며, 더 많은 Ripple Effects를 배제해야 하고, 더 많은 결과를 Review해야 합니다.

장기적으로 이는 Context Acquisition, Model Context, 분석 노력, Verification Scope, Review 노력, 변경당 가변 비용을 높일 수 있습니다. 오늘날 이를 통해 일반적인 경제 곡선을 그릴 수 있을 만큼 강한 연구는 제가 알지 못하며, 그래서 그런 곡선을 만들어 내지도 않을 것입니다.

그래도 Engineering Inference로는 다음이 타당해 보입니다.

Agent가 Big Ball of Mud를 기술적으로 다룰 수 있다는 사실은, 그것을 계속 발전시키는 것이 경제적으로나 사람에게도 여전히 합리적인지 답해 주지 않습니다.

조금 더 날카롭게 말하면:

Big Ball of Mud는 Agent에게 꽤 오랫동안 아무렇지 않을 수 있습니다. 하지만 비용과, 생성된 해결책을 계속 이해해야 하는 사람에게도 그럴지는 모릅니다.

AI는 문제 시스템을 경제적으로 더 이상 유지할 수 없다고 느끼는 시점을 뒤로 미룰 수도 있습니다. 사람은 오늘날 Hidden Knowledge를 쌓아 이런 시스템을 오래 유지합니다. Coding Agent는 그 Navigation의 일부를 자동화할 수 있습니다.

동시에 반대 방향의 역학도 존재합니다. Agent가 변경을 훨씬 빠르게 만들 수 있다면 구조적 문제도 더 빠르게 늘릴 수 있습니다. 더 많은 Output이 자동으로 더 많은 Architecture를 의미하지 않습니다. 빠른 변경 하나하나가 또 다른 로컬 예외를 추가한다면 더 높은 생산성이 구조적 침식을 가속할 수 있습니다.

장기적으로 어느 힘이 더 강할지는 아직 모릅니다.

Agentic Work는 Big Ball of Mud의 기술적 특성을 제거하지 않습니다. 다만 그 경제적 결과를 우리가 얼마나 오래 감수할 수 있는지는 바꿀 수 있습니다.

전체 경제 계산은 이후 경제성 글에서 다룰 문제입니다.

새 기능에서는 같은 비용 변화가 반대 방향으로 작용할 수도 있습니다.

많은 나쁜 아키텍처 결정은 더 나은 구조를 아무도 볼 수 없어서 생기는 것이 아닙니다. 단기적으로 더 싼 길이 매력적이기 때문에 생깁니다. 기존 큰 컴포넌트에 기능 하나 더, 기존 Service에 특수 케이스 하나 더, 같은 Shared Library를 쓰는 Consumer 하나 더, 작은 Dependency 하나 더.

깔끔한 새 Boundary도 결국 시간은 듭니다.

Agent 때문에 Code Production이 더 저렴해진다면 이 계산은 달라질 수 있습니다. 이미 과부하된 컴포넌트에 다음 Feature를 또 넣는 것보다 새로운 작은 자율 단위를 만드는 것이 경제적으로 더 쉬워질 수 있습니다.

이것은 Microservices에 대한 주장이 아닙니다. 자율 단위는 도메인 Slice일 수도 있고 Modular Monolith 안의 Module일 수도 있습니다.

흥미로운 생각은 단순합니다.

더 저렴한 Code Production은 좋은 경계를 만드는 비용도 바꿀 수 있습니다.

이 질문은 이 시리즈의 이후 주제로 바로 이어집니다. Code 자체가 더 이상 소프트웨어 개발에서 가장 비싼 부분이 아닐 때 Software Architecture에는 무슨 일이 생길까요?

오래된 Engineering 아이디어의 새로운 사용자

섹션 제목: “오래된 Engineering 아이디어의 새로운 사용자”

제가 일곱 개의 비슷하지만 다른 Client를 위해 Nx를 사용했을 때 AI 친화적인 Repository를 만들려는 것이 아니었습니다. Build Commands를 표준화했을 때는 Developer Experience가 목적이었습니다. Module Boundaries는 원치 않는 Dependencies를 막기 위한 것이었습니다. 자동화 Tests는 사람에게 더 빠른 Feedback을 주기 위한 것이었습니다. Containers는 로컬 환경을 더 재현 가능하게 만들기 위한 것이었습니다. Infrastructure as Code는 인프라를 이해하고 재현하기 쉽게 만들기 위한 것이었습니다. affected는 처음에는 CI를 더 선택적으로 실행하기 위한 방법이었습니다.

이 모든 것에는 이제 추가적인 사용자가 있습니다. Coding Agent입니다.

Agent는 Contract를 읽고, Project Graph를 분석하고, 표준화된 Target을 실행하고, Boundary Violation을 Lint Error로 받고, Test를 실행하고, Container를 시작하고, Failure를 읽고, Patch를 수정하고, 다시 같은 사이클을 수행할 수 있습니다.

이것이 갑자기 좋은 소프트웨어의 정의를 바꾸는 것은 아닙니다. 하지만 누가 이 특성의 혜택을 받을 수 있는지는 바꿉니다.

따라서 이 작업 공간의 품질은 Context Window가 얼마나 큰지, 다음 모델 세대가 얼마나 똑똑한지에만 달려 있지 않습니다. 일부는 아주 전통적인 Engineering 안에 있습니다. Boundaries, Contracts, Toolchain, Tests, Reproducibility, 그리고 중요한 지식이 실제로 시스템 안에서 얼마나 보이는가에 있습니다.

AI 친화적인 소프트웨어의 많은 특성은 오래된 소프트웨어 엔지니어링 아이디어에 새로운 사용자가 생긴 것입니다.

그래서 이제 관점의 변화가 꽤 명확하게 느껴집니다.

과거 Developer Experience였던 것이 점점 Agent Infrastructure가 되어 갑니다.

어쩌면 AI Readiness는 완전히 새로운 품질 차원이라기보다, 우리 소프트웨어의 품질이 실제로 어디에 존재하는지를 확인하는 추가적인 테스트일지도 모릅니다. 시스템 자체에 있는지, 아니면 오랜 시간 시스템의 약점과 함께 사는 법을 배운 사람들의 머릿속에 주로 있는지 말입니다.

AI 친화적인 소프트웨어는 중요한 지식을 명시적으로 만들고, 작업 공간을 작게 유지하며, Agent가 자신의 변경을 검증할 수 있게 합니다.

  • Nx 문서: Project Graph, Task Graph, Targets, Task Pipelines, dependsOn, run-many, affected, Module Boundaries에 대한 설명은 2026년 9월 기준입니다. 직접 참조: Project Graph와 Task Graph, Targets와 run-many, Task Pipelines와 dependsOn, affected, Module Boundaries. JavaScript / TypeScript Projects에서는 현재 @nx/enforce-module-boundaries를 사용하며, 전체 Project Graph에 대한 언어 간 규칙에는 Conformance Rules가 있습니다.
  • Monorepos: 이 글의 정리는 Google 생태계의 연구를 포함한 대규모 공유 코드베이스에 대한 기존 연구를 참고합니다. Code Visibility, 중앙화된 Toolchain, 공동 변경 같은 장점은 실제 Trade-offs와 함께 존재합니다. 이것이 모든 시스템에서 Monorepo가 더 나은 Repository Strategy라는 뜻은 아닙니다.
  • Agent Retrieval Bench와 ContextBench: 두 연구는 Coding Agent의 Context Acquisition을 서로 다른 관점에서 조사합니다. 관련 Repository Context를 찾고 실제로 사용하는 것이 그 자체로 하나의 작업이라는 주장을 뒷받침하지만, 특정 아키텍처가 자동으로 일정량의 Tokens를 절약한다는 것을 증명하지는 않습니다.
  • SWE-agent, SWE-Gym, SWE-bench: 이 연구 또는 Benchmarks는 Software Engineering Agents를 위해 실행 가능한 Repository, Runtime, Test Environment를 사용합니다. Tool Access와 실행 가능한 Feedback이 있는 Agent가 순수한 Text Generation보다 더 많은 일을 할 수 있다는 메커니즘을 뒷받침합니다. 여기서 일반적인 생산성 주장을 도출하지는 않습니다.
  • Tacit 및 Architectural Knowledge: Hidden 또는 Tacit Knowledge에 대한 설명은 프로그램 이해와 진화를 다룬 Peter Naur의 Programming as Theory Building(1985)과, 결정 지식의 활용과 문서화를 다룬 Tang et al.의 A survey of architecture design rationale(2006)에 기반합니다. 여기서 말하는 것은 Repository에서 자동으로 드러나지 않는 조직 특유의 지식이지, 모델이 일반적인 사전 지식을 전혀 가지고 있지 않다는 주장이 아닙니다.
  • Big Ball of Mud와 경제성: 장기적인 Context, Review, 변경 비용 및 AI가 문제가 있는 시스템의 경제적 생존 기간을 늘릴 수 있는지에 관한 주장은 명시적으로 Engineering Hypotheses입니다. 신뢰할 수 있는 일반 경제 곡선을 만들 수 있을 만큼 충분한 실증적 근거는 현재 없습니다.