Symphony Patterns Itsm Automation

OpenAI Symphony에서 배운 7가지 패턴을 Rails ITSM에 적용한 이야기

AI 에이전트가 티켓을 잡고 방치하는 문제를 겪고 나서, OpenAI의 Symphony 프로젝트를 분석했다. Symphony는 GitHub 이슈 트래커를 폴링하고 코딩 에이전트(Codex, Claude 등)를 자동으로 실행시키는 오케스트레이터인데, 핵심 철학이 인상적이었다: “에이전트를 관리하지 말고, 일(Work)을 관리해라.” 이 철학에서 7가지 패턴을 추출하고, Rails 8 + SolidQueue 기반 ITSM 시스템에 모두 적용했다. 각 패턴이 왜 필요했는지, 어떻게 구현했는지를 실제 코드와 함께 정리한다. 배경: AI 에이전트 방치 사고 문제의 발단은 단순했다. ITSM 시스템에서 티켓을 AI 에이전트에게 배정했는데, 에이전트가 분석을 시작하고 중간에 타임아웃이 났다. 타임아웃 처리 코드가 없었기 때문에 티켓 상태는 assigned로 남았고, 시스템 어디에도 경보가 울리지 않았다. ...

2026-01-16 · 8분 소요 · Seunghan
Rails Turbo Actioncable 500 Debug

Rails Turbo Stream 500 에러 3종 세트 디버깅 — broadcast, SolidCable, Telegram Markdown

Rails 8 + Hotwire(Turbo) 기반 앱을 운영하다 보면 broadcast_append_to 계열 콜백이 조용히 500을 내뱉는 경우가 있다. 거기에 SolidCable 초기 설정 문제와 Telegram Bot 메시지 파싱 오류가 겹치면 로그 해석도 헷갈린다. 이번에 세 가지가 한꺼번에 터져서 순서대로 해결한 과정을 정리한다. 이 글에서 다루는 세 문제는 서로 독립적이지만, 실제 운영 환경에서는 이렇게 한꺼번에 맞닥뜨리는 경우가 많다. 각 문제를 격리해서 하나씩 해결하는 접근이 중요하다. 문제 1: No unique index found for id — broadcast 콜백 500 현상 메시지나 알림을 생성할 때 컨트롤러에서 500이 발생한다. 로그를 보면: ...

2026-01-09 · 7분 소요 · Seunghan
Rails Solidqueue Render Manual Assignment

Rails 8 + SolidQueue Render 배포 삽질 3연타 — 테이블 누락, AI 담당자, 수동 배정

오늘 Rails 8 기반 ITSM 시스템을 Render에 배포하면서 연속으로 삽질을 했다. 각각 원인이 달랐지만 사슬처럼 연결된 문제들이었다. 배포 로그를 보고 디버깅하고, 코드를 고치고, 새로운 문제를 발견하는 과정을 기록해 둔다. 이 글에서 다루는 스택은 Rails 8.1, SolidQueue 1.3.1, Puma, PostgreSQL이고 배포 환경은 Render.com이다. 삽질 1 — Application exited early with SolidQueue 증상 Render 배포 로그에 빌드는 성공인데 실행하자마자 죽는다. ==> Build successful 🎉 ==> Deploying... ==> Running 'bundle exec puma -C config/puma.rb' [87] Puma starting in cluster mode... [87] * Preloading application ==> Application exited early Build successful 메시지가 나왔으니 빌드 단계는 정상이다. 문제는 실행 단계다. Puma 프로세스가 시작조차 못 하고 종료된다. ...

2026-01-06 · 7분 소요 · Seunghan
Rails Ruby3 Kwargs Dispatch Integration Debug

하루 종일 삽질한 것들 — Ruby 3.0 kwargs, Docker env, NAS 크론, SSH 특수문자

AI 에이전트가 Rails API 서버를 호출해서 티켓을 자동 배정하는 디스패처를 만들었다. 로직 자체는 간단한데 붙이는 과정에서 예상치 못한 곳에서 계속 막혔다. 하루 동안 7개의 서로 다른 버그를 순서대로 만났고, 각각은 사소하지만 연속으로 터지니 꽤 피로했다. 비슷한 스택을 쓰는 사람에게 도움이 됐으면 해서 기록해 둔다. 1. Ruby 3.0 kwargs 분리 — render_success(key: val) 가 왜 터지나 가장 오래 고생한 것. Rails 컨트롤러에서 응답 헬퍼를 이렇게 호출했다: render_success(tickets: tickets_list, pagination: pagination_data) 서버 로그에 찍힌 에러: ...

2026-01-02 · 7분 소요 · Seunghan
Hotwire Native Ios Tab Bar Patterns

Hotwire Native iOS 탭바 앱 구축 — HotwireTabBarController 적용기와 삽질 모음

Rails 앱을 Hotwire Native로 래핑할 때 단일 Navigator 대신 HotwireTabBarController 패턴으로 전환하면서 생긴 문제들을 정리한다. 시뮬레이터에서는 안 보이던 버그가 TestFlight에서 터지고, 로컬 개발 환경 설정이 꼬이는 등 여러 지점에서 시간을 날렸다. 1. HotwireTabBarController 기본 구조 단일 Navigator 대신 탭별로 독립적인 Navigator와 WKWebView를 갖는 구조다. // AppTab.swift enum AppTab: String, CaseIterable { case home, ai, request var systemImage: String { switch self { case .home: return "house" case .ai: return "message" case .request: return "checkmark.circle" } } var selectedSystemImage: String { switch self { case .home: return "house.fill" case .ai: return "message.fill" case .request: return "checkmark.circle.fill" } } var url: URL { let base = AppDelegate.baseURL switch self { case .home: return base.appendingPathComponent("dashboard") case .ai: return base.appendingPathComponent("conversations") case .request: return base.appendingPathComponent("service_requests") } } var hotwireTab: HotwireTab { HotwireTab( title: "", image: UIImage(systemName: systemImage)!, selectedImage: UIImage(systemName: selectedSystemImage)!, url: url ) } } // SceneController.swift 핵심 부분 private lazy var tabBarController: HotwireTabBarController = { let controller = HotwireTabBarController(navigatorDelegate: self) controller.load(AppTab.allCases.map(\.hotwireTab)) // 탭 아이콘만 표시, 텍스트 제거 controller.viewControllers?.forEach { vc in vc.tabBarItem.title = nil vc.tabBarItem.imageInsets = UIEdgeInsets(top: 6, left: 0, bottom: -6, right: 0) (vc as? UINavigationController)?.delegate = self } return controller }() 탭 제목을 없애고 아이콘만 남기려면 tabBarItem.title = nil과 imageInsets 조정이 같이 필요하다. title만 nil로 하면 아이콘 위치가 내려가지 않아서 어색하게 보인다. ...

2025-12-26 · 5분 소요 · Seunghan
Rails Denormalized Cache Vs Count Query

Rails 비정규화 캐시 컬럼과 COUNT 쿼리 불일치: 씨드 데이터가 0%를 만들었을 때

Rails 앱에 데모용 씨드 데이터를 직접 삽입했는데, 화면에서 모든 퍼센트가 0% 로 표시되는 상황을 만났다. 서버 로그도 깨끗하고, 데이터는 DB에 분명히 들어가 있는데, 숫자만 안 나온다. 상황 투표 기능이 있는 Rails 앱이다. 선택지(Choice)마다 득표 수를 보여주는 화면이 있고, 전체 투표수 대비 퍼센트를 계산해서 프로그레스 바와 숫자로 표시한다. 데모를 보여줘야 해서 외부 API에서 실시간 데이터를 가져와 씨드 데이터로 넣었다. 방식은 간단했다. # 씨드 데이터: 컬럼을 직접 업데이트 choice.update_column(:vote_count, 4712) pick.update_column(:total_votes, 6536) DB를 직접 조회하면 숫자가 잘 들어가 있다. 그런데 화면에서는: ...

2025-12-16 · 4분 소요 · Seunghan
Mcp Flutter Rails System Category Debug

MCP 도구 연동부터 Flutter 설정 토글까지 — 삽질 기록

MCP 도구로 서버 사이드에 카테고리를 생성했다. 그런데 모바일 앱에서 새 카테고리가 보이지 않았다. 간단해 보이는 문제였는데, 파고들수록 여러 레이어가 얽혀 있었다. 문제의 시작: MCP로 만든 카테고리가 앱에 안 보인다 MCP 도구를 통해 dev/, memory 같은 시스템 카테고리를 서버에 생성했다. API를 직접 호출하면 데이터가 있다. 앱을 리프레시해도 나타나지 않는다. 첫 번째 가설: 앱이 캐시를 사용하는 건가? → 아니다. PapersLoadRequested + PaperCategoriesLoadRequested 이벤트를 순서대로 디스패치하고 있었고, 서버에서 정상 응답이 오고 있었다. 두 번째 가설: API가 필터링하고 있나? → Rails 컨트롤러를 봤다. 필터 없음. 전체 반환 중. ...

2025-12-13 · 5분 소요 · Seunghan
Crypto Exchange Api Integration Lessons

Binance·Bybit·OKX 5개 거래소 API — 공식 문서가 틀렸을 때 Rails에서 살아남기

Ruby on Rails로 여러 암호화폐 거래소의 펀딩레이트(funding rate)를 수집하는 기능을 만들면서 겪은 문제들을 정리한다. 5개 거래소를 붙이면서 각 거래소마다 API 동작 방식이 달랐고, 공식 문서와 실제 동작이 다른 경우도 있었다. 거래소 API의 공통 기반 클라이언트 만들기 여러 거래소를 붙이기 전에 공통 HTTP 클라이언트를 먼저 만들었다. Faraday를 사용했고, 재시도와 Circuit Breaker를 여기에 몰아 넣었다. Faraday + faraday-retry 설정 # Gemfile gem "faraday" gem "faraday-retry" def connection @connection ||= Faraday.new(url: base_url) do |f| f.request :retry, { max: 3, interval: 0.5, backoff_factor: 2, interval_randomness: 0.5, # jitter retry_statuses: [429, 503, 504], retry_block: -> (env, options, retries, exc) { Rails.logger.warn("[#{exchange_name}] Retrying... #{retries} left. Status: #{env.status}") } } f.adapter Faraday.default_adapter f.options.timeout = 10 f.options.open_timeout = 5 end end backoff_factor: 2와 interval_randomness: 0.5(jitter)를 조합하면 재시도 간격이 0.5초 → 1초 → 2초로 지수 증가하면서 약간의 무작위성이 붙는다. 거래소 API가 Rate Limit(429)을 돌려줄 때 모든 클라이언트가 동시에 재시도하는 “thundering herd” 문제를 막아준다. ...

2025-12-06 · 6분 소요 · Seunghan
Api Response Wrapper Token Parsing Debug

로그인이 자꾸 풀린다 — API 래퍼 포맷 불일치가 만든 연쇄 버그

모바일 앱에서 로그인이 자꾸 풀린다. 로그인 직후는 정상인데, 앱을 잠깐 백그라운드로 내렸다가 다시 열면 로그인 화면이 뜬다. SecureStorage에 토큰 저장도 확인했고, Dio 인터셉터로 401 자동 갱신도 구현되어 있는데 왜? 증상 재현 앱 로그인 → 정상 동작 액세스 토큰 만료 시점 전후로 앱 재시작 → 세션 복원 실패, 강제 로그아웃 서버 로그에서 힌트를 찾았다. FormatException: "user" field is missing or null 토큰 갱신 응답을 파싱하다가 터지고 있었다. 구조 파악 서버는 모든 API 응답을 공통 래퍼로 감싼다. ...

2025-12-02 · 4분 소요 · Seunghan
Hotwire Native Webview 8 Fixes

Hotwire Native WebView 삽질 모음 — 네이티브 앱에 Rails WebView 래핑할 때 자주 겪는 8가지 문제

Rails 앱을 Hotwire Native(Turbo Native)로 래핑해서 iOS/Android 네이티브 앱을 만들다 보면, 브라우저에서는 멀쩡한데 WebView에서만 이상하게 동작하는 것들이 꽤 많다. 실제로 작업하면서 겪은 문제와 적용한 수정을 한 곳에 정리해 둔다. 대부분 CSS 몇 줄 또는 path configuration JSON 한 줄로 끝난다. 1. 더블탭 줌 / 300ms 클릭 딜레이 증상 버튼을 빠르게 두 번 탭하면 화면이 확대된다. 단순 탭에도 눌렸다는 느낌이 살짝 늦다 (약 300ms). 원인 iOS WKWebView는 더블탭 줌 제스처를 감지하기 위해 첫 번째 탭 이벤트를 ~300ms 동안 잡아둔다. user-scalable=yes(viewport 기본값) 상태에서는 핀치 줌과 더블탭 줌이 활성화되어 있다. ...

2025-11-25 · 4분 소요 · Seunghan
개인정보처리방침 이용약관 면책조항 문의