AI 에이전트에게 /proc 파일시스템을 부여했다
요약
자율 에이전트의 내부 상태를 모니터링하기 위해 Linux의 /proc 파일시스템 개념을 도입한 사례를 소개합니다. 별도의 API 없이 파일시스템 인터페이스를 통해 에이전트의 실시간 작업, 이벤트, 상태를 직관적으로 관찰하고 디버깅할 수 있습니다.
핵심 포인트
- Linux의 /proc 개념을 차용하여 에이전트 상태를 실시간 뷰로 제공
- 별도의 API 호출 없이 cat, jq 등 기존 도구로 에이전트 내성 가능
- 합성된(synthetic) 파일시스템을 통해 작업 테이블과 이벤트 버스 노출
- 에이전트와 개발자 모두에게 익숙한 파일시스템 인터페이스 활용
자율 에이전트(autonomous agent)를 구축하다 보면, 그것이 정확히 무엇을 하고 있는지 전혀 알 수 없다는 사실을 깨닫는 순간이 옵니다.
철학적인 의미가 아니라, 지루하고 운영적인 측면에서 말입니다. 에이전트는 백그라운드 작업(background job), 세 번의 채팅 턴(chat turns), 그리고 메모리 통합(memory-consolidation) 단계를 동시에 실행하고 있는데, 정작 어떤 작업이 막혀 있는지 알고 싶을 때는 로그 파일을 grep으로 검색하며 수동으로 타임스탬프를 대조해야 합니다. 에이전트는 작업 테이블(task table), 이벤트 버스(event bus), 플러그인 레지스트리(plugin registry)와 같은 풍부한 내부 삶을 가지고 있지만, 그 중 어느 것도 접근할 수 없습니다. 모든 것이 프로세스(process) 내부에 갇혀 있는 것입니다.
그래서 저는 Unix가 1984년에 알아낸 방식을 실행했습니다. 제 에이전트에게 /proc을 부여한 것입니다.
Linux에서 통째로 빌려온 아이디어
Linux에서 /proc은 디스크에 저장되지 않는 파일시스템(filesystem)입니다. /proc/1234/status를 읽을 때, 커널(kernel)은 읽는 그 순간에 답을 _계산(computes)_합니다. 즉, 프로세스 1234의 실시간 상태를 요청 시점에 텍스트로 렌더링하여 보여주는 것입니다. 아무것도 저장되지 않습니다. 그것은 파일이 아니라 뷰(view)입니다. _실시간 상태를 파일시스템으로 본다_는 이 하나의 아이디어 덕분에, 여러분은 이미 가지고 있는 도구들로 실행 중인 커널을 cat 명령어로 살펴볼 수 있는 것입니다.
제 에이전트인 Talon은 Telegram, Discord, 그리고 터미널(terminal) 전반에서 실행되는 장기 실행 프로세스(long-lived process)로, 누군가 말을 걸든 아니든 백그라운드에서 작업을 수행합니다. Talon은 정확히 /proc이 발명된 목적에 부합하는 종류의 내부 상태를 가지고 있습니다. 따라서 ~/.talon/ns에 마운트(mounted)된 네임스페이스(namespace)는 다음과 같은 실시간 뷰를 제공합니다.
~/.talon/ns/
home/ 워크스페이스 (실제 파일)
skills/ 실제 파일
...
home, skills, logs는 일반적인 디렉토리(directory)입니다. 하지만 proc/은 합성된(synthetic) 것입니다. 에이전트가 proc/tasks/<id>를 읽을 때, 디스크에서 아무것도 가져오지 않습니다. 작업 기록은 읽기 시점에 실시간 작업 테이블(live task table)에서 직렬화(serialized)됩니다. proc/events는 이벤트 버스(event bus)의 링 버퍼(ring buffer)이며, 확인하는 순간 JSON Lines 형식으로 렌더링됩니다. 이것은 프로세스가 지금 바로 하고 있는 일의 투영(projection)이며, 경로(path)를 통해 주소 지정이 가능합니다.
그 보상은 Linux가 얻은 것과 동일합니다. 에이전트는 이미 가지고 있는 도구들로 스스로를 내성(introspect)할 수 있습니다. 특별한 API도, 맞춤형 "내 상태 가져오기" 함수도 필요 없습니다. 에이전트는 다른 모든 일에 사용하는 것과 동일한 cat과 jq를 사용하여 cat ~/.talon/ns/proc/events | jq를 실행하고, 자신의 이벤트 스트림을 확인합니다. 제가 에이전트가 무엇을 하고 있는지 알고 싶을 때도, 저 역시 파일을 읽습니다. 인터페이스는 파일시스템(filesystem)이며, 모두가 이미 파일시스템을 다룰 줄 압니다.
왜 API가 아니라 파일시스템인가
저는 이 모든 것을 list_tasks(), get_event_log(), describe_plugins()와 같은 도구(tools)로 노출할 수도 있었습니다. 실제로 많은 에이전트 프레임워크가 그렇게 합니다. 하지만 모든 도구는 모델이 새로 배워야 하는 새로운 요소이며, 새로운 스키마(schema)이자 새로운 호출(call)입니다. 반면 파일시스템은 모델이 이미 완벽하게 알고 있는 대상입니다.
ls, cat, grep, jq를 사용할 수 있는 에이전트는 자신의 내부를 탐색하기 위해 새로운 도구가 전혀 필요하지 않습니다. 이미 가지고 있는 기본 요소(primitives)를 조합하기 때문입니다. 마지막 10개의 이벤트가 필요하다면? tail. 플러그인을 건드리는 모든 태스크가 필요하다면? grep. 멈춰버린 태스크가 필요하다면? cat proc/tasks/<id>. 파일시스템 추상화의 범용성이 바로 핵심입니다. 상태를 경로(paths)로 노출하면, 예상치 못한 부분까지 포함하여 전체 Unix 도구 상자(toolbox)를 공짜로 얻게 됩니다.
이것은 /proc이 그 이후에 나온 모든 "시스템 모니터링 API"를 이긴 것과 같은 이유입니다. API는 몇 개의 문이 달린 벽과 같습니다. 파일시스템은 탁 트인 들판입니다.
어려운 점: FUSE는 항상 지킬 수 있는 약속이 아니다
여기서부터는 단순히 흥미로운 수준을 넘어 엔지니어링의 영역으로 넘어갑니다.
합성 파일(synthetic files)을 제공하려면 FUSE가 필요합니다. 이는 "이 디렉토리는 디스크가 아니라 내 코드로 뒷받침된다"라고 말하는 방식입니다. FUSE는 훌륭하지만 동시에 취약합니다. /dev/fuse가 필요합니다. 사용 중인 Node 버전과 일치하는 네이티브 애드온(native addon)이 필요합니다. 마운트(mount)가 멈추지 않아야 합니다. 컨테이너 내부, 보안이 강화된 호스트, 또는 의존성 재빌드 이후에는 이 중 어느 하나라도 실패할 수 있습니다. 그리고 편의를 위한 뷰를 마운트하지 못해 충돌(crash)이 발생하는 에이전트는 나쁜 거래입니다. 내성(introspection) 계층은 자신이 내성을 수행하는 대상 자체를 무너뜨려서는 안 됩니다.
따라서 마운트(mount)가 실패하는 대신 성능이 저하(degrade)됩니다. 어떤 이유로든 — 설정이 꺼져 있거나, 애드온(addon)이 누락되었거나, /dev/fuse가 없거나, 마운트 프로브(mount probe)가 타임아웃되는 등 — FUSE를 사용할 수 없게 되면, 네임스페이스(namespace)는 **심볼릭 링크 팜 (symlink farm)**으로 폴백(fallback)됩니다. 즉, 실제 디렉토리들(home, skills, logs)이 커널이 네이티브하게 따를 수 있는 일반 심볼릭 링크가 되어, ls ~/.talon/ns/home 명령이 계속 작동하게 됩니다. 합성된 proc/ 뷰(view)는 잃게 되지만, 워크스페이스(workspace)를 잃지는 않으며 시스템이 충돌(crash)하지도 않습니다. FUSE가 정상일 때는 완전한 충실도(full fidelity)를 제공하고, 그렇지 않을 때는 작동 가능한 하위 집합(subset)을 제공합니다. 에이전트는 자신이 서 있는 바닥에 맞춰 적응합니다.
내가 자랑스럽게 생각하는 부분: 스스로 치유한다
부팅 시 성능을 저하시키는 것은 쉽습니다. 진짜 문제는 프로세스가 실행되는 도중에 마운트가 죽을 수 있다는 점입니다. — 네이티브 애드온이 데몬(daemon)이 실행 중인 상태에서 다시 빌드되어 버리거나, 마운트 포인트(mountpoint)가 ENOTCONN 상태로 갇히거나, 커널 측 요소가 사라지는 경우 등입니다. 시작 시점에 건강했던 마운트라고 해서 계속 건강하게 유지되는 마운트는 아닙니다.
그래서 워치독(watchdog)이 일정 간격으로 라이브 뷰(live views)를 다시 프로브(re-probe)합니다. 만약 마운트가 죽은 것을 발견하면, 단순히 로그를 남기고 포기하는 것이 아니라 — 죽은 마운트를 해제하고, 장애 동안에도 워크스페이스에 접근할 수 있도록 심볼릭 링크 팜을 복구한 뒤, 다시 마운트(remount)를 시도합니다. 재마운트에 성공하면 합성된 뷰들이 자동으로 다시 나타납니다. 만약 정해진 횟수만큼 시도한 후에도 복구되지 않으면, 영원히 요동(thrashing)치기보다는 심볼릭 링크 폴백 상태로 완전히 안착합니다. 시스템의 휴식 상태(resting state)는 FUSE의 협조 여부와 상관없이 항상 "작동 중"입니다.
이러한 자기 치유(self-healing) 루프가 데모(demo)와 몇 주 동안 계속 실행해 둘 수 있는 무언가 사이의 차이점입니다. 데모는 한 번 마운트하고 끝납니다. 하지만 데몬은 자신의 발밑에서 환경이 변하더라도 살아남아야 합니다.
이것이 자율 에이전트에게 가져다주는 이점
구체적인 이점은 디버깅 가능성(debuggability)이지만, 더 깊은 차원의 이점은 바로 _조합성(composability)_입니다. 에이전트의 내부 상태가 경로(path)이기 때문에, 경로를 대상으로 작동하는 모든 것은 에이전트의 내부 상태를 대상으로 작동하게 됩니다. 특정 조건을 감시하는 기술은 tail proc/events를 수행할 수 있습니다. 상태 확인(health check)은 합성 파일(synthetic file)에 대해 stat을 실행할 수 있습니다. 제가 아직 구현하지 않은 미래의 기능들은 단 하나의 새로운 API 없이도 이러한 뷰(view)들을 읽을 수 있을 것입니다. 왜냐하면 인터페이스는 API가 아니라 파일시스템(filesystem)이었고, 파일시스템은 설계상 개방적이기 때문입니다.
40년 전 Unix는 라이브 상태(live state)를 노출하는 방법으로 이를 파일처럼 보이게 만드는 방식을 택했습니다. 당시에는 그것이 옳았고, 결과적으로 자기 자신을 관찰해야 하는 AI 에이전트에게도 정확히 옳다는 것이 증명되었습니다. 여러분의 아키텍처에서 가장 훌륭한 아이디어는 종종 누군가가 이미 가졌던 아이디어인 경우가 많습니다. 단지 그것이 자신에게도 적용된다는 사실을 알아차리기만 하면 됩니다.
Talon은 오픈 소스 (MIT) 에이전트형 AI 하네스(agentic AI harness)입니다. Telegram, Discord, Teams, 그리고 터미널 전반에 걸쳐 실제 메모리와 백그라운드 자율성을 갖춘 하나의 지속적인 에이전트로 작동합니다. VFS는 src/core/vfs에 위치합니다. 만약 이런 종류의 작업에 관심이 있다면(catnip): github.com/dylanneve1/talon.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기