본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 20. 11:19

Go를 위한 에이전트 중심 경험 (Agentic Experience for Go)

요약

Go 언어 환경에서 AI 에이전트가 시스템 로그와 데이터를 정확히 이해할 수 있도록 돕는 '에이전트 중심 경험(Agentic Experience)' 설계 방식을 소개합니다. 분산 시스템의 추적 ID 전파와 구조화된 JSON 출력을 통해 에이전트의 디버깅 및 데이터 처리 능력을 극대화하는 ax-go 라이브러리를 제안합니다.

핵심 포인트

  • 에이전트의 디버깅을 위해 서비스 간 추적 ID(trace ID) 전파가 필수적임
  • 인간용 TUI와 에이전트용 JSON 출력을 동일한 소스로 유지하여 데이터 일관성 확보
  • ax-go를 통해 Go 프로젝트에서 에이전트 친화적인 인터페이스를 정형화함

몇 년 전 RightScale에서 근무할 때, 저는 설계 문서보다 깨진 로그 파일로부터 분산 시스템(distributed systems)에 대해 더 많은 것을 배웠습니다. 요청이 프론트엔드 입구로 들어와 워크플로 서비스(workflow service)를 통해 퍼져나가고, 플러그인(plugin)을 거쳐 클라우드 API를 호출합니다. 실패가 발생했을 때, 그것이 어디서 발생했는지 찾는 유일한 방법은 거쳐온 모든 서비스의 로그를 일렬로 늘어놓는 것이었습니다. 그래서 우리는 프론트엔드부터 클라우드 호출에 이르기까지, 그리고 다시 돌아오는 경로에 추적 ID(trace ID)를 스레드(thread)로 연결했습니다. 이 ID가 있으면 실패는 검색 쿼리가 되지만, 없으면 추측에 의존해야 했습니다.

교훈의 귀환

그 교훈은 머릿속에 박혔습니다. 단지 두 번 배워야 했을 뿐입니다.

gRPC 플러그인을 통해 클라우드 제공업체와 통신하는 FinOps CLI인 finfocus를 구축하면서, 저는 똑같은 벽에 부딪혔습니다. 다만 이번에는 로그를 따라갈 수 없는 두 번째 독자가 있었습니다. 바로 에이전트(agent)였습니다. 저는 Claude에게 플러그인 호출이 왜 실패했는지 찾아달라고 요청하곤 했는데, Claude는 finfocus 로그를 뒤지다가 gRPC 경계(boundary)에 도달하면 그 너머에서 아무것도 찾지 못했습니다. 연결되지 않은 추적(traces), 혹은 추적 자체가 아예 없었습니다. 저는 초기에 zerolog를 사용했지만, 잘못 연결했습니다. CLI는 로그를 남겼고, 플러그인도 로그를 남겼습니다. 하지만 두 개를 연결해 주는 것이 없었기에, 우리 중 누구도 경계를 가로질러 볼 수 없었습니다.

추적 ID(trace ID)가 실제로 그 경계를 넘어가게 되자, 문제 해결(troubleshooting) 속도가 빨라졌습니다. 에이전트는 호출을 수행하고, finfocus CLI부터 gRPC 플러그인을 거쳐 다시 돌아오는 과정을 추적하여 어느 쪽에서 문제가 생겼는지 저에게 알려줄 수 있었습니다. 버그 사냥(Bug hunting)은 강령술(séance)에서 grep 수준으로 변했습니다.

두 번째 벽

다음 문제는 더 기묘했습니다. finfocus에는 인간의 눈에 맞게 제작된 예쁜 테이블이 포함된 TUI(Text User Interface)가 있습니다. 에이전트에게 이를 읽으라고 요청하면 숫자를 틀리게 읽는데, 테이블은 파서(parser)가 아니라 사람을 위해 레이아웃이 구성되어 있기 때문입니다. 당시의 에이전트들은 이 작업에 서툴렀습니다.

그래서 테이블을 렌더링하는 모든 명령에는 테이블이 수행하는 것과 _동일한 함수_를 실행하는 --json 옵션도 함께 추가되었습니다. 두 가지 렌더링 방식, 하나의 진실 공급원(source of truth): 인간에게는 예쁘게, 에이전트에게는 구조화된 방식으로 제공됩니다. 합계(total)는 한 번만 계산되기 때문에, 두 방식 간에 합계가 불일치하는 일은 발생할 수 없습니다.

정형화하기

그 후 저는 이 모든 과정을 gh-aw-fleet에서 다시 수행했습니다.

동일한 배관 작업(stream separation, trace propagation, structured errors, 모든 인간용 뷰에 대한 JSON twin)을 세 번째 프로젝트에 복사하고 있을 때쯤, 질문에 대한 답은 스스로 나타났습니다. 왜 프로젝트마다 이것을 다시 작성하고 있는가? 한 번 정형화(Canonize)하여 다른 사람들이 사용할 수 있게 하고, 대중(그리고 기계들)의 지혜를 빌려 더 나은 방향으로 개선하자.

그것이 바로 ax-go: Go를 위한 에이전트 중심 경험 (Agentic Experience for Go)입니다.

무엇인가 (what it is)

ax-go는 단일 Go 패키지(github.com/rshade/ax-go, ax로 임포트)로, 에이전트가 인간만큼 신뢰성 있게 사용할 수 있도록 CLI에 필요한 관례(conventions)를 인코딩합니다. 제가 계속해서 재발견해야 했던 규칙들을 한 번에 기록해 두었습니다:

  • stdout은 데이터이고, stderr는 그 외의 모든 것이다. 최종 JSON 페이로드(payload)만이 stdout에 출력됩니다. 로그, 진행 상황, 에러 엔벨로프(error envelopes)는 stderr로 전송됩니다. 에이전트는 stdout을 파서(parser)로 파이프 연결하고, 사용자는 여전히 로그를 읽습니다.
  • 경계를 넘나드는 트레이스(Traces). W3C Trace Context가 OpenTelemetry를 통해 context.Context를 타고 흐르므로, 하나의 호출이 CLI에서 호출되는 대상까지 상관관계(correlated)를 유지합니다.
  • 동일한 입력, 동일한 바이트. 동일한 입력에 대한 두 번의 실행은 바이트 단위로 일치하는 stdout을 생성합니다. 에이전트는 출력의 차이(diff)를 비교하여 드리프트(drift)를 포착하며, 이는 기계 버전의 신뢰입니다.
  • __schema 명령. 모든 도구는 자신의 명령, 플래그(flags), 타입을 JSON으로 설명할 수 있으므로, 에이전트는 추측하는 대신 스스로 근거를 확보(grounding)할 수 있습니다. MCP 어댑터도 포함되어 있습니다.
  • 에이전트 안전 프리미티브(Agent-safety primitives). 재시도된 생성 작업이 두 번 실행되지 않도록 자동 생성되는 --idempotency-key, 범용적인 --dry-run, 결정론적 종료 코드(deterministic exit codes), 그리고 구조화된 에러 엔벨로프를 제공합니다.

현재 상태 (where it's at)

현재 출시되었으며 1.0 미만 버전입니다. v0.1.0은 고정 가능한(pinnable) 태그이며, 출력 계약(output contracts)이 이미 코드에 동결되어 골든 테스트(golden tests)로 고정되어 있으므로, 에이전트가 의존하는 형태가 갑자기 변하는 일은 없을 것입니다.

제가 사용하는 것들인 zerolog와 OpenTelemetry에서 시작합니다. 다른 구조화된 로거(structured loggers)와 출력 형식들도 뒤따라오겠지만, 저는 아직 테스트해보지 않은 것들을 추측하기보다는 finfocus와 gh-aw-fleet에서 직접 테스트한 방식들을 먼저 출시하고 싶습니다. 이것은 우선 제 자신의 도구들을 위한 공통된 DNA입니다. 만약 이것이 여러분의 도구에도 유용하다면, 더할 나위 없이 좋습니다.

몇 년이 지난 지금도, 저는 여전히 플러그인 경계(plugin boundaries)를 가로질러 트레이스 ID(trace IDs)를 전달하고 있습니다. 유일한 차이점은 이제 그것을 읽는 주체가 누구냐 하는 것입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0