Implementing a retrospective agent: how it works

Alex O'Callaghan

회고 에이전트 구현: 동작 방식

원문은 Alex O'Callaghan님이 에 게재했습니다. 이 블로그 구독하기

이전 글에서 엔지니어링 시간이 어디에 쓰이는지 측정하려는 이유와, 작은 LLM 에이전트가 그 측정을 팀의 스프린트 회고에 어떻게 통합할 수 있는지 설명했다. 몇 달이 지난 지금, 이 에이전트를 전 팀에 배포했고 이전에는 일어나지 않던 논의를 촉발하는 데 도움이 되고 있다.

이번 글에서는 에이전트가 어떻게 동작하고 데이터를 어떻게 받아오며 프롬프트가 어떻게 발전해 왔는지에 집중한다. 그 바탕이 되는 데이터 플랫폼, 특히 DORA 메트릭을 어떻게 도출하는지는 다음 글에서 따로 다룬다.

개요

에이전트는 생산성 메트릭 데이터 플랫폼에서 메트릭과 활동 데이터를 가져와 스프린트를 요약하고 잠재적인 논의 주제를 제안하는 사전 공유용 회고 리포트를 생성한다.

모든 원시 데이터를 모델에 쏟아붓는 대신, 에이전트에는 세 개 스프린트에 걸친 메트릭 요약과 특정 티켓, 머지 리퀘스트, 실패한 릴리스의 세부 정보를 조회할 수 있는 도구만 제공한다.

Metrics summary(sprint + 2 previous)AgentDetail: tickets, MRs,failed releasesRetro report(3-section markdown)always in the promptasks via 3 lookup tools

에이전트 설계

초기 프롬프트

초기 프롬프트는 세 개 스프린트에 걸친 메트릭을 요약하며, 각 스프린트를 [BASELINE] 또는 [LATEST - FOCUS SPRINT]로 태깅한다. 메트릭은 Jira 딜리버리와 DORA 메트릭을 다룬다:

DORA 메트릭Jira 메트릭
배포 빈도이슈 및 스토리 포인트 완료율
변경 리드 타임 (코딩 + 리뷰 + 배포)계획 대비 스프린트 중 추가된 범위
변경 실패율이월
서비스 복구 시간사이클 타임

모든 메트릭은 데이터 플랫폼에서 결정적 코드로 계산되며, 모델에 원시 데이터로부터 직접 계산을 시도하도록 요청하지 않는다. 프롬프트는 이 점을 명확히 한다: "the metrics summary is authoritative, do not attempt to recalculate or second-guess it".

요약에는 사이클 타임이나 리드 타임이 75퍼센타일을 초과하는 경우 등 이상치에 해당하는 티켓과 머지 리퀘스트 목록, 그리고 포커스 스프린트에서 발생한 실패한 릴리스 목록도 포함된다. 에이전트는 이 목록들을 분석의 출발점으로 활용하도록 지시받는다. 티켓은 KEY (16.8d, 3.0SP) 형태로, MR은 project!iid (308.7h: coding 116.3h, review 170.0h, deploy 22.4h) 형태로 인라인으로 표기된다.

조회 도구

PydanticAI 에이전트에는 세부 정보를 조회할 수 있도록 세 가지 도구가 등록되어 있다:

@agent.tool
def lookup_jira_ticket(ctx: RunContext[AgentDeps], ticket_key: str) -> str:
    """Look up a Jira ticket by its key (e.g. PROJ-1363).

    Returns summary, status, assignee, story points, sprint, dates, cycle time,
    and full status transition history.
    """

@agent.tool
def lookup_merge_request(ctx: RunContext[AgentDeps], query: str) -> str:
    """Search merge requests by title substring, Jira key, or MR reference (e.g. !1049).

    Returns up to 10 matching MRs with title, author, dates, lead time, and release info.
    """

@agent.tool
def lookup_failure(ctx: RunContext[AgentDeps], query: str) -> str:
    """Search detected failed releases by tag, project name, evidence, or alert tiny id.

    Returns up to 10 matching failures with signal (alert/revert), evidence,
    resolution time, and correlated alert detail where available.
    """

독스트링은 모델이 추론하는 스키마 역할도 하며, 반환 페이로드가 에이전트가 서사를 구성하기 시작하는 재료가 된다. 티켓은 전체 status_history를 읽기 쉬운 체인 형태(예: "In Progress → Code Review → Awaiting Release → Done")로 반환해 "Blocked" 상태를 들락날락한 티켓을 식별하는 데 도움이 된다. MR은 코딩/리뷰/배포 시간이 나눠져 있고 이를 배포한 릴리스 정보와 함께 반환되어, MR 간의 공통 패턴과 릴리스 사이클에 미치는 영향을 파악하는 데 도움이 된다.

시스템 프롬프트는 에이전트가 이러한 도구를 활용하도록 독려한다: "use these tools proactively ... look it up before drawing conclusions".

출력 구조

에이전트의 출력 타입은 일반 문자열이며, 구조화된 출력 스키마도 후처리도 없다. 리포트를 형성하는 유일한 요소는 프롬프트의 작업 정의다: 최신 스프린트에 대한 간결한 요약, 베이스라인 대비 2~4개의 주목할 만한 트렌드("both positive and negative"), 그리고 3~5개의 구체적인 논의 주제 또는 토론 질문을 세 개의 필수 헤딩 아래에 작성하는 것이다: Sprint Summary, Notable Trends, Retrospective Talking Points.

2~4개의 트렌드와 3~5개의 논의 주제를 지정하면 선별을 강제하게 되고, 세 번째 섹션을 결론이 아닌 질문 형태로 구성하면 리포트가 판정이 아닌 사전 공유 자료로 유지되어, 팀이 트렌드를 해석하고 대응 방안을 결정할 수 있는 전체 맥락을 가지고 있음을 신뢰하게 된다.

일반적인 실행 과정

다음은 일반적인 에이전트 실행 과정을 보여주는 그림이다. 요약에는 11개의 티켓과 MR이 언급되었고, 에이전트는 필요한 세부 정보를 얻기 위해 11번의 조회를 병렬로 수행했다. 리포트는 단 한 번의 LLM 호출로 생성되었으며, 출력은 세 섹션으로 구성된 마크다운 리포트였다.

Metrics summaryAgentLookup tools3 sprints of metrics - 11 tickets & MRs namedlookup_jira_ticket ×8 (outliers, carry-overs, addition)lookup_merge_request ×3 (the p75 MRs)status histories, lead-time legswrites the three-section reportMetrics summaryAgentLookup tools

조회를 통해 리포트에 더 많은 세부 정보가 추가되었다. 사이클 타임 이상치 중 가장 심한 케이스(16.8일)는 두 스프린트에 걸쳐 진행된 것으로 드러났고, 해당 머지 리퀘스트는 첫 커밋부터 릴리스까지 308.7시간이 걸렸다(코딩 116.3시간, 리뷰 170.0시간, 배포 22.4시간으로 구성). 다른 MR 하나는 전체 99.7시간 중 99.5시간을 리뷰에 썼다.

리포트는 병목이 배포가 아니라 리뷰 지연(중앙값 58시간, 이전 스프린트의 약 세 배)에 있음을 파악하고 논의할 구체적인 티켓/MR을 짚어냈다. 또한 릴리스 수가 23개에서 6개로 감소한 반면 변경 실패율은 8.7%에서 0%로 개선된 점을 지적하며, 흐름이 느려진 것이 의도적인 리스크 관리인지 아니면 단순히 변경 사항이 리뷰 뒤에 대기하고 있는 것인지 질문을 던졌다.

전체 실행은 두 번의 LLM 호출로 이루어졌다: 입력 토큰 약 11,400개(두 번째 호출에서는 그 중 3분의 1이 캐시 읽기)와 출력 토큰 2,200개였다. 집필 시점의 GPT-5.4 API 가격 기준으로 약 0.05달러이며, 대부분은 출력 토큰 비용이었다.

실제 프롬프트 엔지니어링

엔지니어 이름은 언급하지 않기

초기 프로토타입에서는 에이전트가 느리게 진행된 티켓을 논할 때 종종 엔지니어를 실명으로 언급했다. 비난 없는 회고 문화를 유지하기 위해 이를 방지하는 가드레일을 프롬프트에 추가했다. 에이전트는 개인의 성과가 아니라 팀 전반의 트렌드와 작업 자체에 집중하도록 지시되었다.

이는 결정적 eval로도 검증할 수 있었다. eval은 입력에서 모든 담당자와 MR 작성자를 추출하고, 그 중 하나라도 출력에 나타나면 실패로 처리한다. 프롬프트와 eval이 함께 이 규칙을 강제한다.

스키마도 프롬프트의 일부다

에이전트는 필드 이름을 기반으로 추론하므로 네이밍이 중요하다. 초기 버전에서는 Jira의 status_category 필드를 기반으로 티켓의 상태와 완료 여부를 잘못 판단하곤 했다. 티켓 스키마에 is_done 불리언을 추가하자 문제가 해결되었는데, 모호한 필드 이름이나 값을 모델이 해석하도록 맡기는 대신 결정적 로직과 데이터 구조로 의미를 인코딩한 덕분이었다.

구조가 지시보다 낫다

프롬프트에는 가설이 최종 산출물임에도 불구하고 "hypothesis"라는 단어가 한 번도 등장하지 않는다. 세 번째 섹션은 결론이 아니라 논의 주제와 토론 질문으로 구성된다. 회고는 사람이 주도해야 하며, 리포트 형식이 모델에 이를 기억하라고 요구하는 대신 그 원칙을 구조적으로 담아낸다.

원래 프롬프트는 핵심 메트릭을 한눈에 볼 수 있는 요약 테이블도 요청했지만, 모델은 매번 다른 형식의 요약을 생성했다. 에이전트 주변에 웹 UI를 구축함으로써 요약 테이블은 생성된 서술 옆에 배치되는 결정적 컴포넌트가 되었다. 출력의 일부가 매번 동일해야 한다면 LLM에 생성을 맡기지 않는 것이 좋다.

결론

회고 에이전트는 우리 팀을 위한 작지만 유용한 도구로, 엔지니어링 회고에서 토론과 성찰을 촉발하는 데 도움이 된다. 이는 프롬프트 엔지니어링과 조회 도구의 활용이 팀 다이내믹스를 존중하고 실행 가능한 인사이트에 집중하는 가치 있는 AI 어시스턴트를 어떻게 만들 수 있는지 보여준다.

그 유용성의 상당 부분은 에이전트가 추론하는 권위 있는 데이터를 제공하는 바탕의 메트릭 데이터 플랫폼에 있다. 이를 위해서는 어떤 메트릭을 추적하고 개발 워크플로에서 어떻게 계산할지에 대한 신중한 선택이 필요하다. 다음 글에서는 그 플랫폼이 어떻게 동작하고 DORA 메트릭을 어떻게 도출하며, 이 모든 것이 어떻게 연결되어 엔지니어링 팀을 지원하는지 자세히 다룰 예정이다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글