<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kyungmin (Lucas) Kim</title>
    <description>The latest articles on DEV Community by Kyungmin (Lucas) Kim (@kimmin1kk).</description>
    <link>https://dev.to/kimmin1kk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3903496%2Fa506704a-1d05-4b83-907f-34baee78b0f1.png</url>
      <title>DEV Community: Kyungmin (Lucas) Kim</title>
      <link>https://dev.to/kimmin1kk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kimmin1kk"/>
    <language>en</language>
    <item>
      <title>Bedrock AgentCore harness 로 에이전트 PoC 후기</title>
      <dc:creator>Kyungmin (Lucas) Kim</dc:creator>
      <pubDate>Fri, 10 Jul 2026 00:34:43 +0000</pubDate>
      <link>https://dev.to/janus/bedrock-agentcore-harness-ro-eijeonteu-poc-hugi-4060</link>
      <guid>https://dev.to/janus/bedrock-agentcore-harness-ro-eijeonteu-poc-hugi-4060</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlnryp3mxlemxvqtsaii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlnryp3mxlemxvqtsaii.png" alt=" " width="799" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;요즘 에이전트 PoC를 한 번이라도 해본 분이라면 비슷한 경험이 있을 겁니다. 모델 호출 루프 자체는 하루면 만드는데, 막상 "여러 사용자에게 서비스하려면?"이라는 질문이 들어오는 순간부터 일이 폭발합니다. 격리된 샌드박스, 세션별 메모리, 도구 인증, 관측성, 시크릿 관리, 컨테이너 빌드까지. 정작 모델의 지능은 잘 작동하는데 그 주변 배선 때문에 프로덕션까지 못 가는 경우가 많았습니다.&lt;/p&gt;

&lt;p&gt;그러던 차에 &lt;a href="https://aws.amazon.com/blogs/machine-learning/amazon-bedrock-agentcore-harness-is-now-generally-available-go-from-idea-to-production-grade-agent-in-minutes/" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore harness is now generally available&lt;/a&gt;라는 글을 읽었습니다. "에이전트가 프로덕션에서 돌아가기 위해 필요한 모든 배선을 두 개의 API 호출 뒤로 숨겨주는 managed abstraction"이라는 설명이 흥미로워서, 바로 AWS 계정에서 직접 만들어 테스트해봤습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgr94w9ri7pak04hpoce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgr94w9ri7pak04hpoce.png" alt=" " width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;간단히 말하면 AgentCore harness는 에이전트 오케스트레이션 루프를 "코드"가 아니라 "설정"으로 다루게 해주는 관리형 레이어입니다. 모델, 도구, 스킬, 메모리, 시스템 프롬프트를 선언적으로 넘기면 AgentCore가 격리된 microVM, 메모리 스토어, ID 토큰 볼트, 관측성 파이프라인까지 묶어서 실행해줍니다. 내부적으로는 AWS의 오픈소스 에이전트 프레임워크인 Strands Agents가 루프를 돌리는 구조입니다.&lt;/p&gt;

&lt;p&gt;여기서, &lt;strong&gt;그냥 Bedrock Agents 쓰면 안되나?&lt;/strong&gt; 라는 생각이 들 수 있어 정리한 비교표입니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Bedrock Agents&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;LangGraph&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Strands Agents (AgentCore Harness)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;아키텍처&lt;/td&gt;
&lt;td&gt;AWS 완전 관리형 (블랙박스)&lt;/td&gt;
&lt;td&gt;그래프 기반 상태 머신 (self-hosted)&lt;/td&gt;
&lt;td&gt;심플 루프 + 도구 위임 (managed or self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;모델&lt;/td&gt;
&lt;td&gt;Bedrock 전용&lt;/td&gt;
&lt;td&gt;아무거나&lt;/td&gt;
&lt;td&gt;Bedrock + OpenAI + Gemini + LiteLLM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;도구 연결&lt;/td&gt;
&lt;td&gt;Lambda Action Group 필수&lt;/td&gt;
&lt;td&gt;직접 코딩&lt;/td&gt;
&lt;td&gt;MCP 생태계 네이티브&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;런타임&lt;/td&gt;
&lt;td&gt;블랙박스&lt;/td&gt;
&lt;td&gt;직접 운영 (EC2/ECS)&lt;/td&gt;
&lt;td&gt;AgentCore microVM 또는 직접 운영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 실행&lt;/td&gt;
&lt;td&gt;X (Lambda 별도)&lt;/td&gt;
&lt;td&gt;직접 구현&lt;/td&gt;
&lt;td&gt;O Code Interpreter 내장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;멀티 모델&lt;/td&gt;
&lt;td&gt;X 에이전트당 1개&lt;/td&gt;
&lt;td&gt;O 노드별 가능&lt;/td&gt;
&lt;td&gt;O 턴별 스위칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리&lt;/td&gt;
&lt;td&gt;세션 내 한정&lt;/td&gt;
&lt;td&gt;직접 구현&lt;/td&gt;
&lt;td&gt;Managed Memory (세션 간 영속, actorId 격리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;탈출구&lt;/td&gt;
&lt;td&gt;X vendor lock-in&lt;/td&gt;
&lt;td&gt;처음부터 코드&lt;/td&gt;
&lt;td&gt;O export → Python 코드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포 복잡도&lt;/td&gt;
&lt;td&gt;낮음 (콘솔 클릭)&lt;/td&gt;
&lt;td&gt;높음 (인프라 직접)&lt;/td&gt;
&lt;td&gt;중간 (API 2개로 끝)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 케이스&lt;/td&gt;
&lt;td&gt;단순 챗봇, RAG Q&amp;amp;A&lt;/td&gt;
&lt;td&gt;복잡한 멀티스텝 워크플로우&lt;/td&gt;
&lt;td&gt;도구 사용 많은 작업 실행형 에이전트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Strands Agents + AgentCore Harness는 중간을 노렸습니다. Strands의 심플한 루프(시스템 프롬프트 → 모델 판단 → 도구 호출 → 반복)는 LangGraph처럼 그래프를 설계할 필요 없이 모델이 알아서 판단하는 구조이고, AgentCore가 그 위에 microVM, 메모리, 관측성을 씌워주니 인프라 코드 없이 프로덕션에 올릴 수 있습니다. 거기다 나중에 export 한 줄로 Strands Python 코드로 변환되니까 lock-in 걱정도 없습니다.&lt;/p&gt;

&lt;p&gt;이제 실제로 써봐야겠죠? &lt;br&gt;
PoC 목표는 "AWS 운영 데이터를 조사해서 분석 결과를 PPT로 만들어주는 에이전트"를 처음부터 끝까지 만들어보는 것이었습니다. 모델 갈아끼우기, 메모리 유지, S3에 결과물 떨어뜨리기까지 전부 한 harness로 끝낼 수 있는지 확인하는 게 핵심이었습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  (A) 설계 의도와 아키텍처
&lt;/h2&gt;

&lt;p&gt;AgentCore harness의 핵심 아이디어는 한 줄로 요약됩니다. &lt;strong&gt;에이전트가 하는 일은 선언하고, 그게 돌아가기 위한 인프라는 AgentCore가 책임진다.&lt;/strong&gt; &lt;code&gt;CreateHarness&lt;/code&gt;로 정의하고 &lt;code&gt;InvokeHarness&lt;/code&gt;로 실행하는, 딱 두 개의 API 호출이 사용자 경계입니다. &lt;/p&gt;

&lt;p&gt;그 뒤에 Runtime(microVM 호스팅), Memory(대화 영속화), Gateway(도구 연결), Browser/Code Interpreter(샌드박스 도구), Identity(자격 증명 볼트), Observability(추적)가 자동으로 묶여 들어갑니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdm21c6xhkij64kg1wj60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdm21c6xhkij64kg1wj60.png" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;저장과 실행 모델이 흥미로운 지점입니다. 모든 세션은 &lt;strong&gt;Firecracker microVM&lt;/strong&gt; 위에서 격리되어 돌아가고, 각 세션은 고유의 파일시스템과 shell을 갖습니다. 같은 &lt;code&gt;runtimeSessionId&lt;/code&gt;로 다시 호출하면 microVM이 재기동되더라도 메모리에 저장된 대화가 자동으로 복원됩니다. 즉, 메시지 히스토리를 직접 들고 다닐 필요가 없습니다. &lt;code&gt;actorId&lt;/code&gt;(사용자를 식별하는 메모리 네임스페이스 키)를 같이 넘기면 사용자별로 메모리 공간이 격리됩니다. 이게 멀티 테넌시 처리의 출발점입니다.&lt;/p&gt;

&lt;p&gt;또 하나 인상적이었던 건 &lt;strong&gt;모델 스위칭&lt;/strong&gt;입니다. harness는 Bedrock(Claude, Nova, Llama, OpenAI on Bedrock 등), OpenAI 직결, Gemini, LiteLLM 호환 프로바이더를 모두 지원하는데, 같은 세션 안에서 턴 단위로 프로바이더를 바꿔도 컨텍스트가 유지됩니다. "계획은 Opus로, 코드는 GPT로, 요약은 Gemini로" 같은 패턴이 설정만으로 가능합니다. 외부 프로바이더의 API 키는 AgentCore Identity의 토큰 볼트에 저장되고, 에이전트 코드는 raw credential을 보지 않습니다.&lt;/p&gt;

&lt;p&gt;저는 이걸 AWS에 어떻게 얹었는지부터 정리해보겠습니다. PoC라 새로 만든 IAM 실행 역할 하나(&lt;code&gt;MyHarnessRole&lt;/code&gt;)에 Bedrock 모델 호출(AmazonBedrockFullAccess), CloudWatch Logs, X-Ray, AgentCore 전체(BedrockAgentCoreFullAccess) 권한을 붙였습니다. &lt;br&gt;
managed policy 4개로 끝났습니다.&lt;br&gt;
다만 참고사항으로, 지정 S3 버킷에 결과물을 올리려면 실행 역할에 해당 버킷의 PutObject 권한을 inline으로 추가해야 합니다. managed policy 4개만으로는 AgentCore runtime 버킷에만 쓸 수 있습니다!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpizi2yw4iz0tq63jwcq4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpizi2yw4iz0tq63jwcq4.png" alt=" " width="784" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;컴퓨트는 AgentCore Runtime이 알아서 처리하므로 EC2를 따로 띄우지 않았고, 호출자(저)는 로컬에서 boto3로 SigV4 호출만 했습니다. 결과물 업로드용으로는 지정 S3 버킷에 PutObject 권한을 inline으로 추가했습니다.&lt;/p&gt;

&lt;p&gt;다음은 가장 단순한 형태의 harness 생성 코드입니다. 모델은 기본값(Claude Sonnet)을 쓰고, 도구로 Code Interpreter와 Browser만 붙였습니다.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# harness 생성: 설정만으로 에이전트 정의
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="n"&gt;control&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore-control&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;control&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_harness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;harnessName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws_analytics_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;executionRoleArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:iam::123456789012:role/MyHarnessRole&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;systemPrompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS 운영 데이터 분석 어시스턴트입니다.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agentcore_code_interpreter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code_interpreter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agentcore_browser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;browser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# skills 미지정 시 비활성, memory 미지정 시 managed memory 자동 생성
&lt;/span&gt;    &lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;awsSkills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paths&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;core-skills/*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;specialized-skills/operations-skills/*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 전체 응답 확인 -&amp;gt; harnessArn, status 키 확인
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8slphspk0rj73py8tt00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8slphspk0rj73py8tt00.png" alt=" " width="799" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;다음은 harness 호출 코드입니다.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-west-2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_harness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;harnessArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:us-west-2:123456789012:harness/aws_analytics_agent-qok4nFzuv0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;session-00000000-0000-0000-0000-000000000001&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actorId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user-username&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;현재 계정의 EC2, S3, Lambda 사용 현황을 분석하고 PPT로 만들어서 s3://test-bucket/output/ 에 업로드해줘&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;usage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;입력: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; / 출력: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;outputTokens&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 토큰&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fux4561o8g24wpvr2c51k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fux4561o8g24wpvr2c51k.png" alt=" " width="799" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;▲ InvokeHarness 한 번으로 리소스 조회 → PPT 생성 → S3 업로드까지 완료&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ymac2sim6lfl473pzfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1ymac2sim6lfl473pzfg.png" alt=" " width="799" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;▲ 지정 버킷에 PPT가 업로드된 모습&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;awsSkills&lt;/code&gt;는 AWS가 큐레이션한 스킬 번들(SDK 사용법, IAM, CloudWatch, EC2 등)을 토글 한 번으로 켜는 기능입니다. 별도 URL이나 네트워크 fetch 없이 harness 내부 런타임에 미리 들어있어서, 에이전트가 "EC2 인스턴스 상태 조회"라는 작업을 만나면 그때 해당 스킬만 컨텍스트로 끌어옵니다. 이 progressive disclosure 방식 덕분에 컨텍스트 윈도우가 불필요한 지침으로 채워지지 않습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  (B) Deep Dive: 주요 이점 및 고려 사항
&lt;/h2&gt;

&lt;p&gt;테스트하면서 가장 체감했던 건 &lt;strong&gt;"바꾸기"가 정말 설정 변경으로 끝난다&lt;/strong&gt;는 점이었습니다. 모델을 Sonnet에서 Opus로 갈아끼우는 작업, 도구를 하나 추가하는 작업, 시스템 프롬프트를 다듬는 작업 모두 컨테이너 재빌드 없이 &lt;code&gt;update-harness&lt;/code&gt; 한 번으로 처리됩니다. PoC에서 모델 비교를 위해 5번 정도 모델을 바꿨는데, 각 변경이 분 단위가 아니라 초 단위였습니다. 컨테이너 빌드/푸시 단계가 사라진 게 가장 컸습니다.&lt;/p&gt;

&lt;p&gt;메모리 동작도 인상적이었습니다. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqujnxya3a3v2he1phyhs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqujnxya3a3v2he1phyhs.png" alt=" " width="799" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;▲ 같은 Session ID로 후속 질문 — 이전 분석 결과를 정확히 기억하고 요약&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateHarness&lt;/code&gt;에 memory를 명시하지 않으면 SEMANTIC + SUMMARIZATION 전략과 30일 이벤트 만료를 가진 managed memory가 자동으로 프로비저닝됩니다. 중요한 건 이게 추상화된 블랙박스가 아니라 &lt;strong&gt;계정 안에 실제로 존재하는 AgentCore Memory 리소스&lt;/strong&gt;라는 점입니다. ARN으로 조회 가능하고, 다른 harness에 붙일 수도 있고, 분석 파이프라인에 그대로 흘려보낼 수도 있습니다. PoC에서 &lt;code&gt;actorId="user-poc-01"&lt;/code&gt;로 첫 대화를 한 뒤 새 세션을 열어 "내가 어제 뭐 물어봤었지?"라고 묻자, 직전 세션에서 추출된 요약과 사실들이 context에 자동 주입되면서 정확히 응답했습니다.&lt;/p&gt;

&lt;p&gt;AWS skills를 켜면 에이전트의 LLM 판단 턴 수가 눈에 띄게 줄었습니다. &lt;br&gt;
스킬에 "Athena 쿼리할 때는 이 절차로", "S3 객체 나열은 이 페이지네이션 패턴으로" &lt;br&gt;
같은 절차적 지식이 미리 들어있어서, 모델이 시행착오 없이 한 번에 올바른 경로를 잡습니다. &lt;br&gt;
tool call 자체는 작업 복잡도에 따라 수십 회까지 나오지만, &lt;br&gt;
불필요한 재시도가 사라진 게 핵심입니다. 토큰 비용도 같이 떨어졌습니다.&lt;/p&gt;

&lt;p&gt;운영 측면에서 짚어둘 고려 사항도 있습니다. 첫째, &lt;strong&gt;콜드 스타트&lt;/strong&gt;입니다. microVM이 새로 뜨는 첫 invocation은 1~3초 정도의 추가 지연이 있었습니다. &lt;code&gt;idleRuntimeSessionTimeout&lt;/code&gt;(기본 900초)을 늘리면 warm 상태를 더 오래 유지할 수 있지만, 그만큼 active-consumption 비용이 늘어납니다. PoC에서는 600초로 줄여서 비용을 우선했습니다. 둘째, &lt;strong&gt;VPC 모드의 NAT 비용&lt;/strong&gt;입니다. EFS나 S3 Files 마운트를 쓰려면 VPC가 필수이고, harness는 매 세션 시작 시 ECR Public에서 컨테이너 이미지를 pull하기 때문에 NAT 게이트웨이가 필요합니다. ECR Public은 VPC endpoint를 지원하지 않습니다. 데이터 전송량이 많지 않은 PoC에서는 무시할 수준이었지만, 프로덕션에서는 미리 계산해두는 게 좋습니다.&lt;/p&gt;

&lt;p&gt;셋째, &lt;strong&gt;shared responsibility&lt;/strong&gt;가 생각보다 넓습니다. &lt;code&gt;InvokeHarness&lt;/code&gt;로 들어오는 입력은 인증을 통과한 모든 호출자에게 신뢰되는 입력으로 취급됩니다. 특히 &lt;code&gt;model.additionalParams&lt;/code&gt; 같은 필드는 그대로 프로바이더에 전달되기 때문에, LiteLLM의 &lt;code&gt;aws_bedrock_runtime_endpoint&lt;/code&gt;나 OpenAI의 &lt;code&gt;extra_headers&lt;/code&gt; 같은 파라미터를 통해 요청 경로 자체가 바뀔 수 있습니다. 외부에 노출하는 경우 애플리케이션 레이어에서 model 필드를 allowlist로 거르는 게 안전합니다. 문서에 명시되어 있는 부분인데, PoC 단계라도 한 번 점검해둘 만한 포인트입니다.&lt;/p&gt;

&lt;p&gt;비용 모델은 active-consumption 기반이라 의외로 합리적이었습니다. Runtime은 vCPU-hour $0.0895, GB-hour $0.00945로 과금되는데, 에이전트가 모델 응답을 기다리는 동안에는 CPU를 거의 안 쓰니까 청구 시간도 짧습니다. PoC 일주일 동안 약 200회 invocation을 돌려서 Runtime 비용은 $3 정도, 모델 추론 비용이 그보다 훨씬 컸습니다. harness 자체에는 별도 요금이 없고, 사용한 primitive 단위로만 청구되는 구조라 비용 추적이 깔끔했습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  (C) 아키텍처 트레이드오프
&lt;/h2&gt;

&lt;p&gt;가장 먼저 고민한 건 &lt;strong&gt;harness vs Runtime 직접 사용&lt;/strong&gt;이었습니다. AgentCore Runtime은 내가 코드를 짜고 컨테이너에 담아서 올리는 서버리스 호스팅이고, harness는 그 위에 Strands 기반 루프를 얹어준 관리형 추상화입니다. 자유도는 Runtime이 높고(루프, 훅, 그래프 형태의 워크플로우 가능), 시작 속도와 운영 부담은 harness가 압도적으로 낮습니다. PoC 목적이 "최소 시간에 프로덕션 형태 확인"이었기 때문에 harness를 선택했습니다. 만약 멀티 에이전트 그래프나 양방향 스트리밍, 커스텀 훅이 필요했다면 Runtime이 답이었을 겁니다.&lt;/p&gt;

&lt;p&gt;다음은 &lt;strong&gt;메모리 옵션&lt;/strong&gt;입니다. managed memory(자동 프로비저닝)와 BYO memory(직접 만든 Memory 리소스 ARN 연결) 중에서, PoC에서는 managed memory를 선택했습니다. 이유는 두 가지인데, 첫째로 30일 만료와 SEMANTIC+SUMMARIZATION 전략 기본값이 PoC에는 충분했고, 둘째로 나중에 &lt;code&gt;UpdateHarness&lt;/code&gt;로 BYO로 갈아탈 수 있는 경로가 명시적으로 열려 있었기 때문입니다. 포기한 건 KMS 고객 키로 암호화하거나 namespace 템플릿을 직접 짜는 자유도였는데, 프로덕션 전환 시점에 BYO로 옮기면 됩니다.&lt;/p&gt;

&lt;p&gt;세 번째는 &lt;strong&gt;파일 출력 전략&lt;/strong&gt;입니다. harness는 session storage(세션 내 임시), &lt;br&gt;
EFS access point(공유), S3 Files access point(양방향 동기화) 세 가지 &lt;br&gt;
파일시스템 마운트를 지원합니다. &lt;/p&gt;

&lt;p&gt;PoC에서는 별도 마운트 없이, 에이전트가 shell 환경에서 boto3 &lt;code&gt;upload_file()&lt;/code&gt;로 &lt;br&gt;
직접 S3에 업로드하는 방식을 썼습니다. Code Interpreter(샌드박스)에는 &lt;br&gt;
AWS 자격증명이 주입되지 않기 때문에, PPT 생성은 Code Interpreter에서, &lt;br&gt;
업로드는 shell 도구로 전환해서 처리합니다. 실행 역할에 해당 버킷 &lt;br&gt;
PutObject 권한만 추가하면 동작했고, VPC나 NAT 없이도 가능했습니다.&lt;/p&gt;

&lt;p&gt;프로덕션에서 여러 harness가 같은 파일을 공유하거나, 양방향 동기화가 &lt;br&gt;
필요하다면 S3 Files access point를 검토하면 됩니다 (VPC 필수).&lt;br&gt;
프로덕션에서 최소 권한으로 좁힐 때의 참고 예시입니다.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AgentCoreMemory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:CreateEvent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"bedrock-agentcore:RetrieveMemoryRecords"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock-agentcore:us-west-2:123456789012:memory/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BedrockModelInvocation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"bedrock:InvokeModel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bedrock:InvokeModelWithResponseStream"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:bedrock:us-west-2::foundation-model/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;마지막은 &lt;strong&gt;versioning과 endpoint&lt;/strong&gt; 전략입니다. harness는 &lt;code&gt;UpdateHarness&lt;/code&gt;를 호출할 때마다 immutable한 버전이 생성되고, &lt;code&gt;DEFAULT&lt;/code&gt; endpoint는 자동으로 최신을 따라갑니다. 명시적으로 &lt;code&gt;PROD&lt;/code&gt;, &lt;code&gt;STAGING&lt;/code&gt; 같은 named endpoint를 만들면 해당 endpoint는 명시적 promotion 전까지 고정됩니다. PoC에서는 DEFAULT만 썼지만, 프로덕션에서는 PROD를 V2에 고정하고 새 변경은 DEFAULT(V3)에서 검증한 뒤 PROD를 V3로 점프시키는 방식이 자연스러워 보였습니다. 롤백도 endpoint를 이전 버전으로 가리키기만 하면 끝납니다.&lt;/p&gt;

&lt;p&gt;다음은 PoC에서 사용한 invoke 호출입니다. 세션 ID를 33자 이상으로 맞춰야 한다는 점만 주의하면 됩니다.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# invoke: 같은 sessionId로 다시 부르면 대화가 이어집니다
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock-agentcore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;ljust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 33자 이상 필수
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_harness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;harnessArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arn:aws:bedrock-agentcore:us-west-2:123456789012:harness/aws_analytics_agent_xxx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;actorId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-poc-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 사용자별 메모리 격리
&lt;/span&gt;    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;지난 7일간 us-west-2의 EC2 CPU 사용률 상위 5개 인스턴스를 분석해줘.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contentBlockDelta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;세션 중간에 모델을 갈아끼우는 것도 시험해봤습니다. 같은 sessionId로 첫 턴은 Sonnet, 두 번째 턴은 Opus를 명시했더니 컨텍스트 손실 없이 매끄럽게 이어졌습니다.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 같은 세션에서 모델만 바꿔 호출 - 컨텍스트 유지됨
&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke_harness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;harnessArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HARNESS_ARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;runtimeSessionId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrockModelConfig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
           &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us.anthropic.claude-opus-4-5-20251101-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
               &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;방금 분석한 결과를 PPT 슬라이드 3장으로 정리해줘.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;관측성은 따로 설정한 게 없는데도 CloudWatch GenAI Observability의 Harnesses 탭에서 세션별 trace가 깔끔하게 보였습니다. 모델 호출, 도구 호출, 메모리 작업이 한 화면에서 시간 순으로 정렬되고, 각 span의 input/output이 inline으로 펼쳐졌습니다. 기존에 로그 그룹 다섯 개를 열어보던 패턴이 한 화면으로 줄었습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  Summary &amp;amp; Final Checklist
&lt;/h2&gt;

&lt;p&gt;PoC에서 내린 주요 결정은 이렇게 정리됩니다. &lt;br&gt;
컴퓨트는 harness가 관리하는 microVM 풀(EC2 직접 운영 없음), &lt;br&gt;
메모리는 managed memory로 시작해 향후 BYO 전환 경로 확보, &lt;br&gt;
파일 출력은 shell에서 boto3로 S3 직접 업로드 (VPC 불필요), &lt;br&gt;
IAM은 managed policy 4개로 빠르게 시작하되 프로덕션 전환 시 최소 권한으로 좁힐 예정, &lt;br&gt;
그리고 endpoint는 PoC에서는 DEFAULT만 사용했습니다.&lt;/p&gt;

&lt;p&gt;복제하려는 분을 위한 체크리스트입니다.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;실행 역할에 managed policy 4개 부여: AmazonBedrockFullAccess, CloudWatchLogsFullAccess, AWSXRayWriteOnlyAccess, BedrockAgentCoreFullAccess. 지정 S3 버킷 사용 시 PutObject 권한 inline 추가.&lt;/li&gt;
&lt;li&gt;CloudWatch Transaction Search를 계정에서 한 번 활성화 (관측성 trace 표시에 필요)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runtimeSessionId&lt;/code&gt;는 33자 이상으로 생성 (uuid + padding)&lt;/li&gt;
&lt;li&gt;VPC 모드 사용 시 NAT 게이트웨이로 &lt;code&gt;public.ecr.aws&lt;/code&gt; 도달 가능한지 확인&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;additionalParams&lt;/code&gt; 같은 모델 설정 필드를 외부에서 받지 않도록 애플리케이션 레이어에서 차단&lt;/li&gt;
&lt;li&gt;비용 통제용으로 &lt;code&gt;maxIterations&lt;/code&gt;, &lt;code&gt;timeoutSeconds&lt;/code&gt;, &lt;code&gt;maxTokens&lt;/code&gt;를 처음부터 보수적으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;가장 놀라웠던 건 "config-to-code graduation" 개념이었습니다. &lt;br&gt;
&lt;code&gt;agentcore export harness --arn &amp;lt;arn&amp;gt;&lt;/code&gt; 한 줄로 Strands 기반 Python 코드가 생성되며, 모델/도구/메모리/스킬 배선이 그대로 보존됩니다. 생성된 코드는 AgentCore Runtime에 그대로 배포하거나, Lambda/ECS/K8s 어디든 self-hosted로 옮길 수 있습니다.처음부터 코드로 시작하지 않아도 되는 안전망이 있다는 점이 가장 마음에 들었던 설계였습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fko03rwtfknq6v6erudkj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fko03rwtfknq6v6erudkj.png" alt=" " width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;다음번에는 두 가지를 다르게 할 것 같습니다. 첫째, &lt;code&gt;actorId&lt;/code&gt;와 세션 ID 관리 전략을 처음부터 정해두는 것입니다. 사용자별 메모리 격리는 actorId 하나로 깔끔하게 처리되지만, 세션 ID를 어떻게 발급하고 재사용할지에 대한 정책이 없으면 대화 연속성이 의도와 달라집니다. 둘째, 컨테이너 이미지를 처음부터 가져가지 말고 기본 환경으로 시작하는 것입니다. 처음에 "어차피 커스텀 의존성 필요하겠지" 하고 Dockerfile부터 만들었는데, 결국 기본 환경의 Python + bash로도 충분했고 ECR 푸시 단계가 사라지자 반복 속도가 두 배는 빨라졌습니다.&lt;/p&gt;

&lt;p&gt;마지막으로, AgentCore harness는 "에이전트가 어렵다"라는 명제의 무게중심을 옮겨준 느낌이었습니다. 어려운 건 루프가 아니라 그 주변 배선이었고, 그 배선을 설정으로 다루게 되면서 "어떤 에이전트를 만들지" 자체에 시간을 쓸 수 있게 됩니다. PoC를 며칠이 아니라 몇 시간 단위로 끊어 돌릴 수 있다는 게 결과적으로 더 많은 아이디어를 시도하게 만들었습니다.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table width="100%"&gt;
   &lt;tbody&gt;
   &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table width="100%"&gt;
   &lt;tbody&gt;
   &lt;tr&gt;
   &lt;td width="128"&gt;&lt;a href="https://www.linkedin.com/in/kyungmin-kim-95b497359/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe1g6a032vh4fk23c2v5f.png" width="574" alt="Kyungmin Kim" height="640"&gt;&lt;/a&gt;&lt;/td&gt;
   &lt;td&gt;
&lt;strong&gt;Kyungmin (Lucas) Kim&lt;/strong&gt;&lt;br&gt;
&lt;small&gt;
주식회사 이테크시스템의 AWS Solutions Architect로서, 실무 중심의 서버리스 엔지니어링과 전략적 AI 전환(AX)의 가교 역할을 하고 있습니다. 단순한 시스템 구현을 넘어 지능적이고 자율적인 환경을 전략적으로 설계하는 데 집중하며, AWS 생태계의 지속적인 발전에 기여하고자, 실전에서 얻은 아키텍처 패턴과 기술적 통찰을 기술 커뮤니티와 적극적으로 공유하고 있습니다.
&lt;/small&gt;
&lt;/td&gt;
   &lt;/tr&gt;
   &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table width="100%"&gt;
   &lt;tbody&gt;
   &lt;tr&gt;
   &lt;td width="128"&gt;&lt;a href="https://www.linkedin.com/in/donghee-kim-478a27297/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzv56qthfb2ot3qobsmno.png" width="800" alt="Donghee Kim" height="1200"&gt;&lt;/a&gt;&lt;/td&gt;
   &lt;td&gt;
&lt;strong&gt;Donghee (Chad) Kim&lt;/strong&gt;&lt;br&gt;
&lt;small&gt;
주식회사 이테크시스템의 AWS Solutions Architect이자 테크 에반젤리스트입니다. 기업 고객이 AWS와 AI를 비즈니스에 효과적으로 도입할 수 있도록 '비즈니스 퍼스트' 관점의 아키텍처 설계와 보안 컴플라이언스를 고려한 클라우드 보안 컨설팅을 지원하고 있으며, AWS Summit 2026에서 AX 기반 상품 전략 플랫폼 구축 사례(PRT302-S) 발표 및 AWS 13x Certified (Golden Jacket) 자격을 보유하고 있습니다.
&lt;/small&gt;
&lt;/td&gt;
   &lt;/tr&gt;
   &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>aws</category>
      <category>bedrockagentcore</category>
      <category>agents</category>
      <category>strands</category>
    </item>
    <item>
      <title>CodeGraph를 EC2/EFS에 올리며 겪은 SQLite 쓰기 경합과 EventBridge 기반 실시간 동기화 해결기</title>
      <dc:creator>Kyungmin (Lucas) Kim</dc:creator>
      <pubDate>Mon, 01 Jun 2026 14:10:57 +0000</pubDate>
      <link>https://dev.to/janus/codegraphreul-ec2efse-olrimyeo-gyeoggeun-sqlite-sseugi-gyeonghabgwa-eventbridge-giban-silsigan-donggihwa-haegyeolgi-1ffm</link>
      <guid>https://dev.to/janus/codegraphreul-ec2efse-olrimyeo-gyeoggeun-sqlite-sseugi-gyeonghabgwa-eventbridge-giban-silsigan-donggihwa-haegyeolgi-1ffm</guid>
      <description>&lt;p&gt;며칠 전 &lt;a href="https://news.hada.io/topic?id=29873" rel="noopener noreferrer"&gt;CodeGraph - AI 코딩 에이전트를 위한 코드 지식 그래프&lt;/a&gt;라는 글을 읽었습니다. Claude Code나 Cursor 같은 AI 코딩 에이전트가 코드베이스를 탐색할 때 매번 grep과 Read를 반복하면서 토큰을 태우는 문제를, 미리 만들어둔 코드 지식 그래프로 해결하겠다는 접근이 흥미로웠습니다. 평균 도구 호출 71% 감소, 토큰 57% 감소, 비용 35% 절감이라는 벤치마크 수치도 눈에 띄었고요.&lt;/p&gt;

&lt;p&gt;CodeGraph는 한마디로 "코드베이스의 심볼,호출,구조를 SQLite에 인덱싱해두고, MCP(Model Context Protocol) 서버로 에이전트에게 질의 인터페이스를 제공하는 도구"입니다. 20개 이상의 언어를 Tree-sitter 기반으로 파싱하고, 파일 변경은 OS 네이티브 이벤트(FSEvents/inotify)로 따라잡습니다. 100% 로컬이라 외부 API 호출이 없는 것도 인상 깊었습니다.&lt;/p&gt;

&lt;p&gt;읽다 보니 "그럼 팀 단위로 공유하려면 어떻게 올려야 할까?"라는 의문이 생겼습니다. 로컬 실행이 기본인 도구를 AWS 위에 어떻게 자연스럽게 올릴지 고민하다가, EC2와 EFS 조합으로 PoC를 진행해봤습니다. 이 글은 그 여정을 정리한 기록입니다.&lt;/p&gt;

&lt;p&gt;테스트 대상 코드베이스는 약 12만 라인의 TypeScript/Python 혼합 모노레포로 잡았습니다. 평소 Claude Code로 작업하면서 "이 함수 어디서 호출해?" 같은 질문에 매번 grep이 4~5번 도는 게 답답했던 코드베이스입니다.&lt;/p&gt;

&lt;h2&gt;
  
  
  (A) 설계 의도와 아키텍처
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1z6xedms3w6fs5x75qf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy1z6xedms3w6fs5x75qf.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;먼저 CodeGraph 자체의 동작 원리부터 정리하면 이렇습니다. 핵심은 &lt;strong&gt;Tree-sitter로 AST를 만들고, 심볼,참조,호출 관계를 추출해 SQLite에 정규화된 그래프 형태로 저장&lt;/strong&gt;하는 것입니다. 노드는 함수/클래스/모듈 같은 심볼이고, 엣지는 호출(calls), 참조(references), 상속(extends) 같은 관계입니다. 여기에 SQLite의 FTS5(Full-Text Search) 가상 테이블을 얹어서 심볼명 검색을 즉시 처리합니다.&lt;/p&gt;

&lt;p&gt;기존 에이전트가 코드를 탐색할 때는 보통 Explore 서브 에이전트가 grep → 파일 후보 추리기 → Read로 본문 읽기 → 또 grep 하는 식으로 반복합니다. 이 과정 하나하나가 도구 호출이고 컨텍스트 윈도우를 갉아먹습니다. CodeGraph의 &lt;code&gt;smart_context&lt;/code&gt; 도구는 이걸 한 번의 호출로 압축합니다. "이 심볼과 관련된 진입점, 호출자, 피호출자, 그리고 실제 코드 스니펫"을 그래프 조인 한 방으로 반환합니다.&lt;/p&gt;

&lt;p&gt;또 하나 중요한 설계는 &lt;strong&gt;항상 최신 상태 유지(Always Fresh)&lt;/strong&gt; 입니다. 인덱스가 오래되면 에이전트가 잘못된 정보를 받게 되니까요. CodeGraph는 OS 네이티브 파일 시스템 이벤트(macOS의 FSEvents, Linux의 inotify)를 구독하고, 디바운싱을 걸어서 변경된 파일만 부분 재파싱합니다. 전체 재인덱싱이 아니라 델타만 갱신하기 때문에 큰 모노레포에서도 부담이 적습니다.&lt;/p&gt;

&lt;p&gt;이걸 AWS에 올리는 구도는 "EC2 1대 + EFS 마운트 + ALB 뒤에 MCP 엔드포인트" 형태로 잡았습니다. 컴퓨트는 t3.medium EC2(개인 PoC)에 ARM 기반 Graviton(t4g.medium)도 비교해봤습니다. 코드베이스는 EFS에 두고 여러 개발자가 동일한 인덱스를 공유합니다. CodeRepo는 CodeCommit이 아니라 GitHub을 그대로 두고, EventBridge로 푸시 이벤트를 받아 EC2 위 동기화 스크립트가 &lt;code&gt;git pull&lt;/code&gt;을 트리거하도록 했습니다. SQLite DB 파일도 EFS에 두긴 했지만 이 부분은 뒤에서 트레이드오프로 다시 다룹니다.&lt;/p&gt;

&lt;p&gt;CodeGraph의 핵심 동작을 개념적으로 단순화하면 다음과 같은 모양입니다. 실제 내부 스키마와는 다를 수 있지만, 심볼-관계 그래프의 구조를 이해하는 데는 이 정도면 충분합니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 심볼 노드: 함수, 클래스, 모듈 단위&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;-- function, class, method 등&lt;/span&gt;
  &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;start_line&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;end_line&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 관계 엣지: 호출, 참조, 상속&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;src_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dst_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;-- calls, references, extends&lt;/span&gt;
  &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- FTS5: 심볼명 즉시 검색용 가상 테이블&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;VIRTUAL&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;symbols_fts&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;fts5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'symbols'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  (B) Deep Dive: 주요 이점 및 고려 사항
&lt;/h2&gt;

&lt;p&gt;테스트 시나리오로 동일한 질문 10개를 두 번 던졌습니다. 첫 번째는 일반 Claude Code, 두 번째는 CodeGraph MCP를 붙인 Claude Code. 질문은 "X 함수의 영향 반경", "Y 라우트 핸들러가 호출하는 외부 서비스", "Z 클래스 계층" 같은 탐색형 질의였습니다. 결과는 도구 호출 수가 평균 11회에서 3.4회로 줄었고, 응답까지 걸린 시간도 체감으로 절반 정도였습니다. 글에서 주장한 70% 감소가 실제 체감과 크게 다르지 않았습니다.&lt;/p&gt;

&lt;p&gt;특히 인상적이었던 건 &lt;strong&gt;Impact Analysis&lt;/strong&gt; 도구였습니다. 실무에서 리팩토링을 할 때 가장 두려운 순간은 "이 함수 시그니처를 바꾸면 어디까지 깨지지?"를 판단하는 시점입니다. 보통은 grep으로 직접 호출처를 찾고, 그 호출처를 또 누가 호출하는지 한 단계 더 파고, 테스트 파일에서 이 함수를 모킹하고 있는 곳은 없는지 확인하는 식으로 3~4단계를 반복합니다. 코드베이스가 크면 이 과정에서 빠뜨리는 곳이 생기고, 그게 프로덕션 장애로 이어지기도 합니다. &lt;/p&gt;

&lt;p&gt;CodeGraph의 Impact Analysis는 그래프에서 N홉 BFS(너비 우선 탐색)를 돌려서 한 번에 영향 반경을 반환합니다. 예를 들어 formatDate라는 유틸 함수의 파라미터를 바꾸고 싶다면, "이 함수를 직접 호출하는 곳(1홉) + 그 호출자를 다시 호출하는 곳(2홉) + 관련 테스트 파일"을 한 번의 도구 호출로 트리 형태로 보여줍니다. 단순히 목록만 나열하는 게 아니라 호출 깊이와 파일 경로, 라인 번호까지 포함되어 있어서 에이전트가 바로 수정 계획을 세울 수 있었습니다. 변경 전 영향 분석에 들어가는 사고 비용이 확실히 줄었고, 무엇보다 "빠뜨린 곳이 없다"는 확신을 그래프 구조가 보장해준다는 점이 심리적으로 큰 차이였습니다.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpayls64syz40kif0yxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpayls64syz40kif0yxf.png" alt=" " width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;위 스크린샷처럼, codegraph impact 한 번이면 직접 호출처(1홉)부터 간접 호출처(2홉 이상)까지 트리로 펼쳐줍니다. 단순 텍스트 검색(grep)과 달리 호출 그래프 기반이라 런타임에 실제로 영향받는 심볼만 정확히 잡아내는 것이 핵심입니다.&lt;/p&gt;

&lt;p&gt;또 하나 체감이 컸던 건 smart_context의 코드 스니펫 반환 방식입니다. 기존에는 에이전트가 파일 전체를 읽어서 컨텍스트 윈도우를 낭비하는 경우가 많았는데, CodeGraph는 심볼의 시작 라인과 끝 라인을 알고 있으니 해당 함수 본문만 정확히 잘라서 반환합니다. 500줄짜리 파일에서 20줄짜리 함수만 필요한 상황이라면 컨텍스트 절약 효과가 극적입니다. &lt;/p&gt;

&lt;p&gt;반면 한계도 분명했습니다. 첫째, &lt;strong&gt;동적 호출과 메타프로그래밍에는 약합니다.&lt;/strong&gt; Python의 &lt;code&gt;getattr&lt;/code&gt; 기반 디스패치나TypeScript의 Proxy를 통한 호출은 정적 분석으로는 잡히지 않습니다. 그래프가 비어있는 게 아니라, "이 코드는 자주 직접 호출되지 않네?"라는 잘못된 신호를 줄 수 있습니다. 에이전트가 그래프를 과신할 때 오히려 위험할 수 있다는 뜻입니다. &lt;br&gt;
+실제로 테스트 중 데코레이터 패턴으로 감싸진 핸들러 함수가 그래프에서 "호출자 없음"으로 나타나는 케이스를 발견했고, 이런 경우에는 여전히 grep 기반 탐색이 보완적으로 필요했습니다.&lt;/p&gt;

&lt;p&gt;둘째, AWS 운영 관점에서는 &lt;strong&gt;SQLite 동시 쓰기 문제&lt;/strong&gt;가 가장 신경 쓰였습니다. EFS에 SQLite 파일을 두고 여러 EC2에서 마운트하면, EFS의 NFS 락 의미론과 SQLite의 파일 락이 충돌해서 쓰기 경합이 발생합니다. 결국 인덱서는 단일 EC2에서만 동작시키고, 다른 인스턴스는 읽기 전용으로만 EFS를 마운트하는 구조로 정리했습니다. WAL 모드를 켰지만 NFS 위에서는 권장되지 않아서 일반 rollback journal 모드로 되돌렸습니다.&lt;/p&gt;

&lt;p&gt;셋째, &lt;strong&gt;콜드 스타트 비용&lt;/strong&gt;입니다. 12만 라인 모노레포 첫 인덱싱이 t3.medium에서 약 6분 걸렸습니다. Graviton(t4g.medium)으로 옮기니 4분대로 줄었지만, 어쨌든 EC2를 끄고 켤 때마다 인덱싱을 다시 하면 안 되니 EFS에 DB를 영속화하는 게 필수였습니다. 그 대신 EFS의 IOPS 비용이 추가됩니다. PoC 한 달 비용을 따져보니 EC2(t4g.medium 24시간 운영) 약 $25, EFS(약 2GB + IOPS) $5 정도로 개인 부담은 가벼운 편이었습니다.&lt;/p&gt;

&lt;p&gt;넷째, &lt;strong&gt;MCP 서버를 외부에 노출할 때의 보안&lt;/strong&gt;입니다. CodeGraph는 100% 로컬을 전제로 만들어졌기 때문에 인증 레이어가 없습니다. 팀에 공유하려면 ALB 앞단에 OIDC 인증을 붙이거나, 최소한 VPN/Tailscale 같은 사설 네트워크 안에서만 접근하도록 막아야 합니다. 저는 PoC라 ALB + Cognito OIDC 조합으로 막아뒀습니다.&lt;/p&gt;
&lt;h2&gt;
  
  
  (C) 아키텍처 트레이드오프
&lt;/h2&gt;

&lt;p&gt;가장 먼저 고민한 건 &lt;strong&gt;Lambda vs EC2&lt;/strong&gt;였습니다. MCP 서버는 stdio 또는 long-lived HTTP 연결로 도는 게 자연스럽고, 파일 시스템 이벤트 구독이라는 동작 모델이 Lambda와 맞지 않았습니다. 파일 시스템 워처가 살아있어야 하니 상시 실행되는 컴퓨트가 필요했고, 결국 EC2를 택했습니다. ECS Fargate도 후보였지만, 파일 시스템 이벤트가 컨테이너 레이어 위에서 어떻게 동작할지 검증할 시간이 부족해서 단순한 EC2로 갔습니다.&lt;/p&gt;

&lt;p&gt;다음은 &lt;strong&gt;EFS vs EBS&lt;/strong&gt; 선택입니다. EBS가 IOPS도 좋고 SQLite 친화적이지만, 저는 "팀원이 자기 노트북에서도 동일한 코드베이스 마운트해서 보고 싶을 수 있다"는 시나리오를 가정했기 때문에 EFS를 골랐습니다. 대신 인덱서는 단일 라이터 정책을 강제했고, EFS Provisioned Throughput은 켜지 않고 Bursting 모드로 운영했습니다. 12만 라인 정도면 Bursting 크레딧으로 충분했습니다. 만약 코드베이스가 100만 라인 단위라면 EBS gp3 + AMI 스냅샷으로 가는 게 맞다고 봅니다.&lt;/p&gt;

&lt;p&gt;세 번째는 &lt;strong&gt;Git 동기화 방법&lt;/strong&gt;입니다. cron으로 5분마다 &lt;code&gt;git pull&lt;/code&gt;을 돌릴까, GitHub Webhook → API Gateway → Lambda → SSM Run Command로 이벤트 기반으로 갈까 고민했습니다. 결국 EventBridge + SSM 조합으로 갔습니다. 푸시가 없을 때는 아무 일도 일어나지 않고, 푸시가 있을 때만 EC2에서 &lt;code&gt;git pull&lt;/code&gt;이 실행되니 인덱서가 OS 이벤트를 자연스럽게 받아 부분 재인덱싱합니다. cron보다 깔끔했지만, 대신 처음 IAM 권한 잡는 데 시간이 좀 들었습니다.&lt;/p&gt;

&lt;p&gt;Neptune이나 OpenSearch 도입은 과도한 인프라 비용과 불필요한 리소스 낭비를 초래하는 오버엔지니어링이라 판단하여 배제했습니다.&lt;br&gt;
현재 규모에서 관리형 DB가 제공하는 확장성은 높은 유지 비용 대비 실질적인 ROI를 보장하지 못합니다. 콜드 스타트 없는 경량 아키텍처라는 본질에 집중하고 비용 효율성을 극대화하기 위해, SQLite 기반의 미니멈 아키텍처를 그대로 보존하는 전략을 채택했습니다.&lt;/p&gt;

&lt;p&gt;EC2 위에 CodeGraph를 설치하는 user-data 스크립트는 이렇게 단순합니다. 한 줄 설치를 표방하는 만큼 부트스트랩이 가볍습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# EFS 마운트 (코드베이스 + DB 영속화 위치)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/code
mount &lt;span class="nt"&gt;-t&lt;/span&gt; efs fs-0abc123:/ /mnt/code

&lt;span class="c"&gt;# CodeGraph 설치 (OS별 번들 자체 포함, Node.js 불필요)&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh

&lt;span class="c"&gt;# MCP 서버를 systemd 서비스로 상시 가동&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; /etc/systemd/system/codegraph.service
[Service]
ExecStart=/usr/local/bin/codegraph mcp --workspace /mnt/code/repo
Restart=always
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; codegraph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub 푸시를 EC2 동기화로 연결하는 EventBridge 룰 설정은 이런 모양입니다. SSM Run Command를 타깃으로 잡았습니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws.partner/github.com/myorg"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"DetailType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GitHub Push Event"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Targets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ssm:ap-northeast-2::document/AWS-RunShellScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RoleArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:role/EventBridgeSSMRole"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RunCommandParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"RunCommandTargets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tag:Role"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Values"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"codegraph"&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Input"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;commands&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;cd /mnt/code/repo &amp;amp;&amp;amp; git pull&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MCP 클라이언트 쪽(예: Claude Code) 설정은 ALB 엔드포인트를 가리키도록 합니다. Cognito 인증을 붙였기 때문에 토큰을 헤더로 넘깁니다.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"codegraph"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://codegraph.internal.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${COGNITO_ID_TOKEN}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary &amp;amp; Final Checklist
&lt;/h2&gt;

&lt;p&gt;핵심 결정을 요약하면 이렇습니다. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;MCP 서버는 상시 실행이 필요하므로 EC2(가능하면 Graviton). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;인덱스 DB는 EFS에 영속화하되, 단일 라이터 정책 유지. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git 동기화는 EventBridge + SSM으로 이벤트 기반. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;외부 노출은 ALB + OIDC 인증으로 최소 보호. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Neptune 같은 매니지드 그래프 DB로의 치환은 의도적으로 거부.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;원활한 PoC를 위한 체크리스트입니다. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;EC2 보안 그룹은 ALB에서 오는 트래픽만 허용.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;EFS 보안 그룹은 EC2 SG에서 2049 포트만 허용. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQLite는 NFS 위에서 WAL 모드 끄기.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;인덱서 EC2는 IMDSv2 강제, SSM 에이전트 활성화. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cognito 토큰 만료 시간을 짧게 잡고 클라이언트에서 갱신.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: Lessons Learned
&lt;/h2&gt;

&lt;p&gt;가장 의외였던 건 "정적 분석 그래프 하나로도 에이전트의 행동 패턴이 이렇게 달라지나"였습니다. 그동안 RAG나 임베딩 기반 검색에만 관심이 쏠려 있었는데, 코드처럼 강한 구조가 있는 도메인에서는 임베딩보다 그래프가 더 정확한 컨텍스트를 적은 토큰으로 줄 수 있다는 걸 확인했습니다. 한국에서도 코드 어시스턴트 도입이 늘면서 토큰 비용이 슬슬 부담되는 시점인데, 이런 결정론적 인덱스 레이어가 다음 단계로 자연스럽게 보입니다.&lt;/p&gt;

&lt;p&gt;다음에 다시 한다면 두 가지를 바꾸겠습니다. &lt;br&gt;
첫째, EFS 대신 EBS gp3 + 일일 스냅샷으로 가서 SQLite의 동시성 이슈를 아예 회피하겠습니다. 팀 공유는 어차피 ALB 뒤 단일 인스턴스로도 충분합니다. &lt;br&gt;
둘째, 인덱서를 ECS Fargate로 옮기는 실험을 해보겠습니다. 파일 시스템 이벤트가 컨테이너 안에서 잘 작동하는지만 확인되면 운영 부담이 더 줄어들 것 같습니다.&lt;/p&gt;

&lt;p&gt;마지막으로 댓글에서 누군가 "그래프류가 너무 많아진다"고 했는데, 그 말도 일리는 있습니다. 다만 CodeGraph처럼 외부 의존성 없이 SQLite 한 파일로 끝나는 디자인은 PoC 비용이 거의 0에 가깝다는 게 큰 장점입니다. 일단 붙여보고 안 맞으면 떼면 그만이라는 점, 이게 도구 선택에서 의외로 중요한 요소라는 걸 다시 느꼈습니다.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>codegraph</category>
      <category>mcp</category>
      <category>ec2efs</category>
    </item>
  </channel>
</rss>
